From 2e50fbc8ae3ef84a5abd03fa7ffdded01ccd3718 Mon Sep 17 00:00:00 2001 From: witzig Date: Sun, 19 Mar 2017 13:36:57 +0100 Subject: [PATCH 1/8] Custom Forms --- app.js | 7 +- languages/mailtrain.pot | 935 +- lib/dbcheck.js | 53 +- lib/helpers.js | 178 +- lib/mailer.js | 46 +- lib/models/fields.js | 5 + lib/models/forms.js | 283 + lib/models/lists.js | 2 +- lib/models/subscriptions.js | 67 +- lib/tools.js | 51 + meta.json | 2 +- package.json | 2 + public/ace/mode-css.js | 1 + public/ace/mode-plain_text.js | 1 + public/ace/worker-css.js | 1 + public/bootstrap/themes/flatly.min.css | 4 +- public/javascript/autosubmit.js | 6 - public/javascript/cookie.2.1.3.js | 156 + public/javascript/editor.js | 29 + public/javascript/jquery-ui-1.12.1.min.js | 13 + public/subscription/footer-scripts.js | 48 + public/subscription/form-input-style.css | 215 + routes/forms.js | 275 + routes/lists.js | 24 +- routes/subscription.js | 452 +- setup/sql/upgrade-00022.sql | 41 + views/emails/confirm-html.hbs | 8478 ----------------- views/emails/password-reset-html.hbs | 8466 +--------------- views/emails/subscription-confirmed-html.hbs | 8477 ---------------- views/emails/unsubscribe-confirmed-html.hbs | 8475 ---------------- views/lists/edit.hbs | 16 + views/lists/forms/create.hbs | 27 + views/lists/forms/edit.hbs | 286 + views/lists/forms/forms.hbs | 87 + views/lists/lists.hbs | 14 +- views/lists/view.hbs | 8 +- views/subscription/capture-flash-messages.hbs | 1 + views/subscription/confirm-notice.hbs | 17 - views/subscription/layout.hbs | 79 - views/subscription/layout.mjml.hbs | 64 + views/subscription/mail-confirm-html.mjml.hbs | 17 + .../mail-confirm-text.hbs} | 0 .../mail-subscription-confirmed-html.mjml.hbs | 18 + .../mail-subscription-confirmed-text.hbs} | 0 .../mail-unsubscribe-confirmed-html.mjml.hbs | 17 + .../mail-unsubscribe-confirmed-text.hbs} | 2 +- views/subscription/manage-address.hbs | 25 - views/subscription/manage.hbs | 129 - .../partials/subscription-custom-fields.hbs | 143 + .../partials/subscription-flash-messages.hbs | 20 + .../subscription-manage-address-form.hbs | 22 + .../partials/subscription-manage-form.hbs | 19 + .../partials/subscription-subscribe-form.hbs | 24 + .../subscription-unsubscribe-form.hbs | 20 + views/subscription/subscribe.hbs | 142 - views/subscription/subscribed.hbs | 21 - views/subscription/unsubscribe-notice.hbs | 9 - views/subscription/unsubscribe.hbs | 27 - views/subscription/updated-notice.hbs | 12 - .../subscription/web-confirm-notice.mjml.hbs | 13 + .../subscription/web-manage-address.mjml.hbs | 13 + views/subscription/web-manage.mjml.hbs | 16 + views/subscription/web-subscribe.mjml.hbs | 13 + views/subscription/web-subscribed.mjml.hbs | 13 + .../web-unsubscribe-notice.mjml.hbs | 13 + views/subscription/web-unsubscribe.mjml.hbs | 16 + .../subscription/web-updated-notice.mjml.hbs | 13 + 67 files changed, 3335 insertions(+), 34834 deletions(-) create mode 100644 lib/models/forms.js create mode 100644 public/ace/mode-css.js create mode 100644 public/ace/mode-plain_text.js create mode 100644 public/ace/worker-css.js delete mode 100644 public/javascript/autosubmit.js create mode 100644 public/javascript/cookie.2.1.3.js create mode 100644 public/javascript/jquery-ui-1.12.1.min.js create mode 100644 public/subscription/footer-scripts.js create mode 100644 public/subscription/form-input-style.css create mode 100644 routes/forms.js create mode 100644 setup/sql/upgrade-00022.sql delete mode 100644 views/emails/confirm-html.hbs delete mode 100644 views/emails/subscription-confirmed-html.hbs delete mode 100644 views/emails/unsubscribe-confirmed-html.hbs create mode 100644 views/lists/forms/create.hbs create mode 100644 views/lists/forms/edit.hbs create mode 100644 views/lists/forms/forms.hbs create mode 100644 views/subscription/capture-flash-messages.hbs delete mode 100644 views/subscription/confirm-notice.hbs delete mode 100644 views/subscription/layout.hbs create mode 100644 views/subscription/layout.mjml.hbs create mode 100644 views/subscription/mail-confirm-html.mjml.hbs rename views/{emails/confirm-text.hbs => subscription/mail-confirm-text.hbs} (100%) create mode 100644 views/subscription/mail-subscription-confirmed-html.mjml.hbs rename views/{emails/subscription-confirmed-text.hbs => subscription/mail-subscription-confirmed-text.hbs} (100%) create mode 100644 views/subscription/mail-unsubscribe-confirmed-html.mjml.hbs rename views/{emails/unsubscribe-confirmed-text.hbs => subscription/mail-unsubscribe-confirmed-text.hbs} (87%) delete mode 100644 views/subscription/manage-address.hbs delete mode 100644 views/subscription/manage.hbs create mode 100644 views/subscription/partials/subscription-custom-fields.hbs create mode 100644 views/subscription/partials/subscription-flash-messages.hbs create mode 100644 views/subscription/partials/subscription-manage-address-form.hbs create mode 100644 views/subscription/partials/subscription-manage-form.hbs create mode 100644 views/subscription/partials/subscription-subscribe-form.hbs create mode 100644 views/subscription/partials/subscription-unsubscribe-form.hbs delete mode 100644 views/subscription/subscribe.hbs delete mode 100644 views/subscription/subscribed.hbs delete mode 100644 views/subscription/unsubscribe-notice.hbs delete mode 100644 views/subscription/unsubscribe.hbs delete mode 100644 views/subscription/updated-notice.hbs create mode 100644 views/subscription/web-confirm-notice.mjml.hbs create mode 100644 views/subscription/web-manage-address.mjml.hbs create mode 100644 views/subscription/web-manage.mjml.hbs create mode 100644 views/subscription/web-subscribe.mjml.hbs create mode 100644 views/subscription/web-subscribed.mjml.hbs create mode 100644 views/subscription/web-unsubscribe-notice.mjml.hbs create mode 100644 views/subscription/web-unsubscribe.mjml.hbs create mode 100644 views/subscription/web-updated-notice.mjml.hbs diff --git a/app.js b/app.js index fa5c76d7..ba4e27bd 100644 --- a/app.js +++ b/app.js @@ -29,6 +29,7 @@ let templates = require('./routes/templates'); let campaigns = require('./routes/campaigns'); let links = require('./routes/links'); let fields = require('./routes/fields'); +let forms = require('./routes/forms'); let segments = require('./routes/segments'); let triggers = require('./routes/triggers'); let webhooks = require('./routes/webhooks'); @@ -54,6 +55,7 @@ if (config.www.proxy) { app.disable('x-powered-by'); hbs.registerPartials(__dirname + '/views/partials'); +hbs.registerPartials(__dirname + '/views/subscription/partials/'); /** * We need this helper to make sure that we consume flash messages only @@ -80,7 +82,9 @@ hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer let rows = []; messages[key].forEach(message => { - rows.push(hbs.handlebars.escapeExpression(message)); + message = hbs.handlebars.escapeExpression(message); + message = message.replace(/(\r\n|\n|\r)/gm, '
'); + rows.push(message); }); if (rows.length > 1) { @@ -205,6 +209,7 @@ app.use('/campaigns', campaigns); app.use('/settings', settings); app.use('/links', links); app.use('/fields', fields); +app.use('/forms', forms); app.use('/segments', segments); app.use('/triggers', triggers); app.use('/webhooks', webhooks); diff --git a/languages/mailtrain.pot b/languages/mailtrain.pot index 5da70af6..ae41f1d1 100644 --- a/languages/mailtrain.pot +++ b/languages/mailtrain.pot @@ -8,11 +8,10 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2017-03-12 00:22+0000\n" +"POT-Creation-Date: 2017-03-19 12:36+0000\n" #: views/archive/layout.hbs:1 #: views/layout.hbs:1 -#: views/subscription/layout.hbs:1 msgid "Self hosted email newsletter app" msgstr "" @@ -36,6 +35,9 @@ msgstr "" #: views/lists/fields/create.hbs:1 #: views/lists/fields/edit.hbs:1 #: views/lists/fields/fields.hbs:1 +#: views/lists/forms/create.hbs:1 +#: views/lists/forms/edit.hbs:1 +#: views/lists/forms/forms.hbs:1 #: views/lists/lists.hbs:1 #: views/lists/segments/create.hbs:1 #: views/lists/segments/edit.hbs:1 @@ -64,7 +66,7 @@ msgstr "" #: views/users/forgot.hbs:1 #: views/users/login.hbs:1 #: views/users/reset.hbs:1 -#: app.js:172 +#: app.js:176 msgid "Home" msgstr "" @@ -84,7 +86,7 @@ msgstr "" #: views/campaigns/unsubscribed.hbs:2 #: views/campaigns/upload-attachment.hbs:2 #: views/campaigns/view.hbs:2 -#: lib/tools.js:119 +#: lib/tools.js:122 #: routes/campaigns.js:35 msgid "Campaigns" msgstr "" @@ -118,7 +120,7 @@ msgstr "" #: views/campaigns/opened.hbs:7 #: views/campaigns/unsubscribed.hbs:7 #: views/lists/subscription/import-failed.hbs:8 -#: views/lists/view.hbs:18 +#: views/lists/view.hbs:19 #: views/triggers/triggered.hbs:6 msgid "Address" msgstr "" @@ -132,9 +134,8 @@ msgstr "" #: views/lists/subscription/add.hbs:6 #: views/lists/subscription/edit.hbs:7 #: views/lists/subscription/import-preview.hbs:7 -#: views/lists/view.hbs:19 -#: views/subscription/manage.hbs:4 -#: views/subscription/subscribe.hbs:4 +#: views/lists/view.hbs:20 +#: views/subscription/partials/subscription-custom-fields.hbs:3 #: views/triggers/triggered.hbs:7 msgid "First Name" msgstr "" @@ -148,9 +149,8 @@ msgstr "" #: views/lists/subscription/add.hbs:7 #: views/lists/subscription/edit.hbs:8 #: views/lists/subscription/import-preview.hbs:8 -#: views/lists/view.hbs:20 -#: views/subscription/manage.hbs:5 -#: views/subscription/subscribe.hbs:5 +#: views/lists/view.hbs:21 +#: views/subscription/partials/subscription-custom-fields.hbs:4 #: views/triggers/triggered.hbs:8 msgid "Last Name" msgstr "" @@ -197,6 +197,7 @@ msgstr "" #: views/lists/create.hbs:5 #: views/lists/edit.hbs:6 #: views/lists/fields/fields.hbs:6 +#: views/lists/forms/forms.hbs:6 #: views/lists/lists.hbs:5 #: views/lists/segments/segments.hbs:6 #: views/templates/templates.hbs:5 @@ -214,6 +215,8 @@ msgstr "" #: views/campaigns/view.hbs:72 #: views/lists/create.hbs:7 #: views/lists/edit.hbs:10 +#: views/lists/forms/edit.hbs:9 +#: views/lists/forms/forms.hbs:7 #: views/lists/lists.hbs:8 #: views/mosaico/editor.hbs:3 #: views/partials/merge-tag-reference.hbs:4 @@ -228,16 +231,16 @@ msgstr "" #: views/campaigns/campaigns.hbs:10 #: views/campaigns/view.hbs:73 -#: views/lists/view.hbs:21 -#: views/lists/view.hbs:29 +#: views/lists/view.hbs:22 +#: views/lists/view.hbs:30 #: views/triggers/triggers.hbs:6 msgid "Status" msgstr "" #: views/campaigns/campaigns.hbs:11 #: views/campaigns/view.hbs:74 -#: views/lists/view.hbs:22 #: views/lists/view.hbs:23 +#: views/lists/view.hbs:24 msgid "Created" msgstr "" @@ -369,8 +372,7 @@ msgstr "" #: views/lists/subscription/add.hbs:12 #: views/lists/subscription/edit.hbs:11 #: views/lists/subscription/import-preview.hbs:5 -#: views/subscription/manage.hbs:10 -#: views/subscription/subscribe.hbs:10 +#: views/subscription/partials/subscription-custom-fields.hbs:9 #: views/templates/create.hbs:8 #: views/triggers/create-select.hbs:7 #: views/triggers/create.hbs:17 @@ -569,8 +571,10 @@ msgstr "" #: views/campaigns/edit-rss.hbs:24 #: views/campaigns/edit-triggered.hbs:27 #: views/campaigns/edit.hbs:35 -#: views/lists/edit.hbs:13 +#: views/lists/edit.hbs:16 #: views/lists/fields/edit.hbs:39 +#: views/lists/forms/edit.hbs:31 +#: views/lists/forms/forms.hbs:12 #: views/lists/segments/edit.hbs:14 #: views/lists/segments/rule-edit.hbs:38 #: views/lists/subscription/edit.hbs:17 @@ -625,7 +629,8 @@ msgstr "" #: views/campaigns/edit.hbs:32 #: views/campaigns/view.hbs:66 #: views/lists/fields/fields.hbs:12 -#: views/lists/view.hbs:32 +#: views/lists/forms/forms.hbs:9 +#: views/lists/view.hbs:33 msgid "No data available in table" msgstr "" @@ -663,7 +668,7 @@ msgstr "" #: views/campaigns/unsubscribed.hbs:11 #: views/campaigns/view.hbs:26 #: views/lists/subscription/import.hbs:10 -#: routes/lists.js:171 +#: routes/lists.js:187 msgid "Unsubscribed" msgstr "" @@ -725,7 +730,7 @@ msgid "List subscribers who received this message" msgstr "" #: views/campaigns/view.hbs:22 -#: routes/lists.js:171 +#: routes/lists.js:187 msgid "Bounced" msgstr "" @@ -888,55 +893,22 @@ msgid "" "that entry and it will be listed here" msgstr "" -#: views/emails/confirm-html.hbs:1 -#: views/emails/confirm-html.hbs:2 -#: views/emails/confirm-text.hbs:1 -msgid "Please Confirm Subscription" -msgstr "" - -#: views/emails/confirm-html.hbs:3 -#: views/emails/confirm-text.hbs:2 -msgid "Yes, subscribe me to this list" -msgstr "" - -#: views/emails/confirm-html.hbs:4 -msgid "" -"If you received this email by mistake, simply delete it. You won't be " -"subscribed if you don't click the confirmation link above." -msgstr "" - -#: views/emails/confirm-html.hbs:5 -#: views/emails/confirm-text.hbs:4 -#: views/emails/subscription-confirmed-html.hbs:7 -#: views/emails/subscription-confirmed-text.hbs:7 -#: views/emails/unsubscribe-confirmed-html.hbs:5 -#: views/emails/unsubscribe-confirmed-text.hbs:5 -msgid "For questions about this list, please contact:" -msgstr "" - -#: views/emails/confirm-text.hbs:3 -msgid "" -"If you received this email by mistake, simply delete it. You won't be " -"subscribed unless you click the confirmation link above." -msgstr "" - #: views/emails/password-reset-html.hbs:1 -#: views/emails/password-reset-html.hbs:2 #: views/emails/password-reset-text.hbs:1 msgid "Change your password" msgstr "" -#: views/emails/password-reset-html.hbs:3 +#: views/emails/password-reset-html.hbs:2 #: views/emails/password-reset-text.hbs:2 msgid "We have received a password change request for your Mailtrain account:" msgstr "" -#: views/emails/password-reset-html.hbs:4 +#: views/emails/password-reset-html.hbs:3 #: views/emails/password-reset-text.hbs:3 msgid "Reset password" msgstr "" -#: views/emails/password-reset-html.hbs:5 +#: views/emails/password-reset-html.hbs:4 #: views/emails/password-reset-text.hbs:4 msgid "" "If you did not ask to change your password, then you can ignore this email " @@ -953,10 +925,11 @@ msgstr "" #: views/emails/stationery-html.hbs:4 #: views/emails/stationery-text.hbs:4 #: views/lists/subscription/edit.hbs:15 -#: views/subscription/manage.hbs:12 -#: views/subscription/unsubscribe.hbs:1 -#: views/subscription/unsubscribe.hbs:4 -#: routes/lists.js:253 +#: views/subscription/partials/subscription-unsubscribe-form.hbs:2 +#: views/subscription/web-manage.mjml.hbs:3 +#: views/subscription/web-unsubscribe.mjml.hbs:1 +#: views/subscription/web-unsubscribe.mjml.hbs:3 +#: routes/lists.js:269 msgid "Unsubscribe" msgstr "" @@ -976,63 +949,6 @@ msgstr "" msgid "Cheers," msgstr "" -#: views/emails/subscription-confirmed-html.hbs:1 -#: views/emails/subscription-confirmed-text.hbs:1 -#: views/subscription/subscribed.hbs:1 -msgid "Subscription Confirmed" -msgstr "" - -#: views/emails/subscription-confirmed-html.hbs:2 -#: views/emails/subscription-confirmed-text.hbs:2 -#: views/subscription/subscribed.hbs:2 -msgid "Your subscription to our list has been confirmed." -msgstr "" - -#: views/emails/subscription-confirmed-html.hbs:3 -#: views/emails/subscription-confirmed-text.hbs:3 -msgid "If you want to modify your subscription then you can:" -msgstr "" - -#: views/emails/subscription-confirmed-html.hbs:4 -#: views/emails/subscription-confirmed-text.hbs:4 -#: views/subscription/subscribed.hbs:6 -msgid "manage your preferences" -msgstr "" - -#: views/emails/subscription-confirmed-html.hbs:5 -#: views/emails/subscription-confirmed-text.hbs:5 -#: views/subscription/subscribed.hbs:5 -#: views/users/login.hbs:10 -msgid "or" -msgstr "" - -#: views/emails/subscription-confirmed-html.hbs:6 -#: views/emails/subscription-confirmed-text.hbs:6 -msgid "unsubscribe here" -msgstr "" - -#: views/emails/unsubscribe-confirmed-html.hbs:1 -#: views/emails/unsubscribe-confirmed-text.hbs:1 -msgid "You are now unsubscribed" -msgstr "" - -#: views/emails/unsubscribe-confirmed-html.hbs:2 -#: views/emails/unsubscribe-confirmed-text.hbs:2 -msgid "We have removed your email address from our list." -msgstr "" - -#: views/emails/unsubscribe-confirmed-html.hbs:3 -#: views/emails/unsubscribe-confirmed-text.hbs:3 -msgid "If you unsubscribed by mistake, you can re-subscribe at:" -msgstr "" - -#: views/emails/unsubscribe-confirmed-html.hbs:4 -#: views/emails/unsubscribe-confirmed-text.hbs:4 -#: views/lists/subscription/add.hbs:16 -#: routes/lists.js:253 -msgid "Subscribe" -msgstr "" - #: views/index.hbs:1 msgid "List Management" msgstr "" @@ -1062,7 +978,7 @@ msgstr "" #: views/lists/fields/edit.hbs:3 #: views/lists/fields/fields.hbs:3 #: views/lists/fields/fields.hbs:5 -#: views/lists/view.hbs:5 +#: views/lists/view.hbs:6 msgid "Custom Fields" msgstr "" @@ -1140,7 +1056,7 @@ msgid "" msgstr "" #: views/index.hbs:25 -#: lib/tools.js:123 +#: lib/tools.js:126 msgid "Automation" msgstr "" @@ -1243,6 +1159,9 @@ msgstr "" #: views/lists/fields/create.hbs:2 #: views/lists/fields/edit.hbs:2 #: views/lists/fields/fields.hbs:2 +#: views/lists/forms/create.hbs:2 +#: views/lists/forms/edit.hbs:2 +#: views/lists/forms/forms.hbs:2 #: views/lists/lists.hbs:2 #: views/lists/lists.hbs:4 #: views/lists/segments/create.hbs:2 @@ -1258,7 +1177,7 @@ msgstr "" #: views/lists/subscription/import-preview.hbs:2 #: views/lists/subscription/import.hbs:2 #: views/lists/view.hbs:2 -#: lib/tools.js:111 +#: lib/tools.js:114 msgid "Lists" msgstr "" @@ -1276,7 +1195,7 @@ msgstr "" #: views/lists/edit.hbs:3 #: views/lists/edit.hbs:4 -#: views/lists/view.hbs:7 +#: views/lists/view.hbs:8 msgid "Edit List" msgstr "" @@ -1293,6 +1212,21 @@ msgid "This is the list ID displayed to the subscribers" msgstr "" #: views/lists/edit.hbs:12 +msgid "Custom Form" +msgstr "" + +#: views/lists/edit.hbs:13 +#: views/lists/forms/forms.hbs:11 +msgid "Default Mailtrain Form" +msgstr "" + +#: views/lists/edit.hbs:14 +msgid "" +"The custom form used for this list. You can create a form here." +msgstr "" + +#: views/lists/edit.hbs:15 msgid "Delete List" msgstr "" @@ -1484,12 +1418,14 @@ msgid "Delete Field" msgstr "" #: views/lists/fields/fields.hbs:7 -#: views/lists/view.hbs:25 +#: views/lists/view.hbs:26 msgid "Type" msgstr "" #: views/lists/fields/fields.hbs:10 #: views/lists/fields/fields.hbs:11 +#: views/lists/forms/edit.hbs:23 +#: views/lists/forms/forms.hbs:8 #: views/lists/lists.hbs:9 #: views/lists/segments/segments.hbs:8 #: views/lists/segments/view.hbs:12 @@ -1498,11 +1434,150 @@ msgstr "" #: routes/campaigns.js:287 #: routes/campaigns.js:576 #: routes/campaigns.js:626 -#: routes/lists.js:222 +#: routes/lists.js:238 #: routes/triggers.js:297 msgid "Edit" msgstr "" +#: views/lists/forms/create.hbs:3 +#: views/lists/forms/edit.hbs:3 +#: views/lists/forms/forms.hbs:3 +#: views/lists/forms/forms.hbs:5 +#: views/lists/view.hbs:5 +msgid "Custom Forms" +msgstr "" + +#: views/lists/forms/create.hbs:4 +msgid "Create Form" +msgstr "" + +#: views/lists/forms/create.hbs:5 +#: views/lists/forms/forms.hbs:4 +msgid "Create Custom Form" +msgstr "" + +#: views/lists/forms/create.hbs:6 +#: views/lists/forms/create.hbs:7 +#: views/lists/forms/edit.hbs:7 +#: views/lists/forms/edit.hbs:8 +msgid "Form Name" +msgstr "" + +#: views/lists/forms/create.hbs:8 +msgid "Add Form" +msgstr "" + +#: views/lists/forms/edit.hbs:4 +msgid "Edit Form" +msgstr "" + +#: views/lists/forms/edit.hbs:5 +msgid "Edit Custom Form" +msgstr "" + +#: views/lists/forms/edit.hbs:6 +msgid "Back to forms" +msgstr "" + +#: views/lists/forms/edit.hbs:10 +msgid "Optional comments about this form" +msgstr "" + +#: views/lists/forms/edit.hbs:11 +msgid "Form Preview" +msgstr "" + +#: views/lists/forms/edit.hbs:12 +msgid "" +"Note: These links are solely for a quick preview. If you submit a preview " +"form you'll get redirected to the list's default form." +msgstr "" + +#: views/lists/forms/edit.hbs:13 +#: views/lists/subscription/add.hbs:16 +#: views/subscription/mail-unsubscribe-confirmed-html.mjml.hbs:4 +#: views/subscription/mail-unsubscribe-confirmed-text.hbs:4 +#: routes/forms.js:150 +#: routes/lists.js:269 +msgid "Subscribe" +msgstr "" + +#: views/lists/forms/edit.hbs:14 +msgid "Confirm Notice" +msgstr "" + +#: views/lists/forms/edit.hbs:15 +msgid "Updated Notice" +msgstr "" + +#: views/lists/forms/edit.hbs:16 +msgid "Unsubscribed Notice" +msgstr "" + +#: views/lists/forms/edit.hbs:17 +msgid "Manage" +msgstr "" + +#: views/lists/forms/edit.hbs:18 +msgid "Manage Address" +msgstr "" + +#: views/lists/forms/edit.hbs:19 +msgid "Create a test user for additional options" +msgstr "" + +#: views/lists/forms/edit.hbs:20 +#: views/templates/create.hbs:2 +#: views/templates/edit.hbs:2 +#: views/templates/templates.hbs:2 +#: views/templates/templates.hbs:4 +#: lib/tools.js:118 +msgid "Templates" +msgstr "" + +#: views/lists/forms/edit.hbs:21 +msgid "Fields" +msgstr "" + +#: views/lists/forms/edit.hbs:22 +msgid "Help" +msgstr "" + +#: views/lists/forms/edit.hbs:24 +msgid "Form Fields" +msgstr "" + +#: views/lists/forms/edit.hbs:25 +msgid "Fields hidden on subscription page:" +msgstr "" + +#: views/lists/forms/edit.hbs:26 +msgid "Fields shown on subscription page:" +msgstr "" + +#: views/lists/forms/edit.hbs:27 +msgid "Fields hidden on preferences page:" +msgstr "" + +#: views/lists/forms/edit.hbs:28 +msgid "Fields shown on preferences page:" +msgstr "" + +#: views/lists/forms/edit.hbs:29 +msgid "" +"The MJML Documentation can be found here." +msgstr "" + +#: views/lists/forms/edit.hbs:30 +msgid "Delete Form" +msgstr "" + +#: views/lists/forms/forms.hbs:10 +msgid "The default form for this list is:" +msgstr "" + #: views/lists/lists.hbs:6 msgid "ID" msgstr "" @@ -1519,8 +1594,8 @@ msgstr "" #: views/lists/segments/segments.hbs:3 #: views/lists/segments/segments.hbs:5 #: views/lists/segments/view.hbs:3 -#: views/lists/view.hbs:6 -#: views/lists/view.hbs:13 +#: views/lists/view.hbs:7 +#: views/lists/view.hbs:14 msgid "Segments" msgstr "" @@ -1562,7 +1637,7 @@ msgstr "" #: views/lists/segments/edit.hbs:4 #: views/lists/segments/edit.hbs:5 #: views/lists/segments/view.hbs:6 -#: views/lists/view.hbs:11 +#: views/lists/view.hbs:12 msgid "Edit Segment" msgstr "" @@ -1706,7 +1781,7 @@ msgid "Match" msgstr "" #: views/lists/segments/view.hbs:5 -#: views/lists/view.hbs:12 +#: views/lists/view.hbs:13 msgid "Segment" msgstr "" @@ -1728,8 +1803,7 @@ msgid "Add subscriber" msgstr "" #: views/lists/subscription/add.hbs:5 -#: views/subscription/manage.hbs:2 -#: views/subscription/subscribe.hbs:3 +#: views/subscription/partials/subscription-custom-fields.hbs:1 #: views/users/account.hbs:7 msgid "Email Address" msgstr "" @@ -1738,8 +1812,7 @@ msgstr "" #: views/lists/subscription/edit.hbs:9 #: views/settings.hbs:82 #: views/settings.hbs:97 -#: views/subscription/manage.hbs:7 -#: views/subscription/subscribe.hbs:7 +#: views/subscription/partials/subscription-custom-fields.hbs:6 msgid "Begins with" msgstr "" @@ -1786,8 +1859,8 @@ msgstr "" #: views/lists/subscription/edit.hbs:6 #: views/lists/subscription/import-preview.hbs:6 -#: views/subscription/unsubscribe.hbs:3 -#: lib/helpers.js:25 +#: views/subscription/partials/subscription-unsubscribe-form.hbs:1 +#: lib/helpers.js:38 #: lib/models/segments.js:11 msgid "Email address" msgstr "" @@ -1851,7 +1924,7 @@ msgid "Categorize the imported subscribers as" msgstr "" #: views/lists/subscription/import.hbs:8 -#: routes/lists.js:171 +#: routes/lists.js:187 msgid "Subscribed" msgstr "" @@ -1871,7 +1944,7 @@ msgstr "" msgid "List Actions" msgstr "" -#: views/lists/view.hbs:8 +#: views/lists/view.hbs:9 #: views/triggers/create-select.hbs:3 #: views/triggers/create-select.hbs:4 #: views/triggers/create.hbs:3 @@ -1881,53 +1954,53 @@ msgstr "" msgid "Create Trigger" msgstr "" -#: views/lists/view.hbs:9 +#: views/lists/view.hbs:10 msgid "Add Subscriber" msgstr "" -#: views/lists/view.hbs:10 +#: views/lists/view.hbs:11 msgid "Import Subscribers" msgstr "" -#: views/lists/view.hbs:14 +#: views/lists/view.hbs:15 msgid "Create New Segment" msgstr "" -#: views/lists/view.hbs:15 +#: views/lists/view.hbs:16 msgid "Filter" msgstr "" -#: views/lists/view.hbs:16 +#: views/lists/view.hbs:17 msgid "Subscriptions" msgstr "" -#: views/lists/view.hbs:17 +#: views/lists/view.hbs:18 msgid "Imports" msgstr "" -#: views/lists/view.hbs:24 +#: views/lists/view.hbs:25 #: routes/campaigns.js:266 -#: routes/lists.js:265 +#: routes/lists.js:281 msgid "Finished" msgstr "" -#: views/lists/view.hbs:26 +#: views/lists/view.hbs:27 msgid "Added" msgstr "" -#: views/lists/view.hbs:27 +#: views/lists/view.hbs:28 msgid "Updated" msgstr "" -#: views/lists/view.hbs:28 +#: views/lists/view.hbs:29 msgid "Failed" msgstr "" -#: views/lists/view.hbs:30 +#: views/lists/view.hbs:31 msgid "Are you sure? This action should only be called to resolve stalled imports" msgstr "" -#: views/lists/view.hbs:31 +#: views/lists/view.hbs:32 msgid "Restart" msgstr "" @@ -2423,128 +2496,210 @@ msgid "" "are not signed." msgstr "" -#: views/subscription/confirm-notice.hbs:1 -#: views/subscription/subscribe.hbs:1 +#: views/subscription/mail-confirm-html.mjml.hbs:1 +#: views/subscription/mail-confirm-text.hbs:1 +msgid "Please Confirm Subscription" +msgstr "" + +#: views/subscription/mail-confirm-html.mjml.hbs:2 +#: views/subscription/mail-confirm-text.hbs:2 +msgid "Yes, subscribe me to this list" +msgstr "" + +#: views/subscription/mail-confirm-html.mjml.hbs:3 +msgid "" +"If you received this email by mistake, simply delete it. You won't be " +"subscribed if you don't click the confirmation link above." +msgstr "" + +#: views/subscription/mail-confirm-html.mjml.hbs:4 +#: views/subscription/mail-confirm-text.hbs:4 +#: views/subscription/mail-subscription-confirmed-html.mjml.hbs:8 +#: views/subscription/mail-subscription-confirmed-text.hbs:7 +#: views/subscription/mail-unsubscribe-confirmed-html.mjml.hbs:5 +#: views/subscription/mail-unsubscribe-confirmed-text.hbs:5 +msgid "For questions about this list, please contact:" +msgstr "" + +#: views/subscription/mail-confirm-text.hbs:3 +msgid "" +"If you received this email by mistake, simply delete it. You won't be " +"subscribed unless you click the confirmation link above." +msgstr "" + +#: views/subscription/mail-subscription-confirmed-html.mjml.hbs:1 +#: views/subscription/mail-subscription-confirmed-text.hbs:1 +#: views/subscription/web-subscribed.mjml.hbs:1 +msgid "Subscription Confirmed" +msgstr "" + +#: views/subscription/mail-subscription-confirmed-html.mjml.hbs:2 +msgid "Your subscription to our list has been confirmed" +msgstr "" + +#: views/subscription/mail-subscription-confirmed-html.mjml.hbs:3 +msgid "If you want to modify your subscription then you can " +msgstr "" + +#: views/subscription/mail-subscription-confirmed-html.mjml.hbs:4 +#: views/subscription/mail-subscription-confirmed-text.hbs:4 +msgid "manage your preferences" +msgstr "" + +#: views/subscription/mail-subscription-confirmed-html.mjml.hbs:5 +#: views/subscription/mail-subscription-confirmed-text.hbs:5 +#: views/users/login.hbs:10 +msgid "or" +msgstr "" + +#: views/subscription/mail-subscription-confirmed-html.mjml.hbs:6 +#: views/subscription/mail-subscription-confirmed-text.hbs:6 +msgid "unsubscribe here" +msgstr "" + +#: views/subscription/mail-subscription-confirmed-html.mjml.hbs:7 +#: views/subscription/web-confirm-notice.mjml.hbs:3 +#: views/subscription/web-subscribed.mjml.hbs:4 +#: views/subscription/web-unsubscribe-notice.mjml.hbs:3 +#: views/subscription/web-updated-notice.mjml.hbs:3 +msgid "Return to our website" +msgstr "" + +#: views/subscription/mail-subscription-confirmed-text.hbs:2 +#: views/subscription/web-subscribed.mjml.hbs:2 +msgid "Your subscription to our list has been confirmed." +msgstr "" + +#: views/subscription/mail-subscription-confirmed-text.hbs:3 +msgid "If you want to modify your subscription then you can:" +msgstr "" + +#: views/subscription/mail-unsubscribe-confirmed-html.mjml.hbs:1 +#: views/subscription/mail-unsubscribe-confirmed-text.hbs:1 +msgid "You Are Now Unsubscribed" +msgstr "" + +#: views/subscription/mail-unsubscribe-confirmed-html.mjml.hbs:2 +msgid "We have removed your email address from our list" +msgstr "" + +#: views/subscription/mail-unsubscribe-confirmed-html.mjml.hbs:3 +#: views/subscription/mail-unsubscribe-confirmed-text.hbs:3 +msgid "If you unsubscribed by mistake, you can re-subscribe at:" +msgstr "" + +#: views/subscription/mail-unsubscribe-confirmed-text.hbs:2 +msgid "We have removed your email address from our list." +msgstr "" + +#: views/subscription/partials/subscription-custom-fields.hbs:2 +msgid "want to change it?" +msgstr "" + +#: views/subscription/partials/subscription-custom-fields.hbs:5 +msgid "Download signature verification key" +msgstr "" + +#: views/subscription/partials/subscription-custom-fields.hbs:7 +msgid "Insert your GPG public key here to encrypt messages sent to your address" +msgstr "" + +#: views/subscription/partials/subscription-custom-fields.hbs:8 +msgid "optional" +msgstr "" + +#: views/subscription/partials/subscription-flash-messages.hbs:1 +#: views/subscription/partials/subscription-flash-messages.hbs:3 msgid "Warning!" msgstr "" -#: views/subscription/confirm-notice.hbs:2 +#: views/subscription/partials/subscription-flash-messages.hbs:2 msgid "If JavaScript was not enabled then no confirmation message was sent" msgstr "" -#: views/subscription/confirm-notice.hbs:3 -msgid "Almost finished." +#: views/subscription/partials/subscription-flash-messages.hbs:4 +msgid "JavaScript must be enabled in order for this form to work" msgstr "" -#: views/subscription/confirm-notice.hbs:4 -msgid "" -"We need to confirm your email address. To complete the subscription " -"process, please click the link in the email we just sent you." -msgstr "" - -#: views/subscription/confirm-notice.hbs:5 -#: views/subscription/unsubscribe-notice.hbs:3 -#: views/subscription/updated-notice.hbs:3 -msgid "return to our website" -msgstr "" - -#: views/subscription/manage-address.hbs:1 -msgid "Update your Email Address" -msgstr "" - -#: views/subscription/manage-address.hbs:2 +#: views/subscription/partials/subscription-manage-address-form.hbs:1 msgid "Existing Email Address" msgstr "" -#: views/subscription/manage-address.hbs:3 +#: views/subscription/partials/subscription-manage-address-form.hbs:2 msgid "New Email Address" msgstr "" -#: views/subscription/manage-address.hbs:4 +#: views/subscription/partials/subscription-manage-address-form.hbs:3 msgid "Your new email address" msgstr "" -#: views/subscription/manage-address.hbs:5 +#: views/subscription/partials/subscription-manage-address-form.hbs:4 msgid "" "You will receive a confirmation request to your new email address that you " "need to accept before your email is actually changed" msgstr "" -#: views/subscription/manage-address.hbs:6 +#: views/subscription/partials/subscription-manage-address-form.hbs:5 +#: views/subscription/web-manage-address.mjml.hbs:2 msgid "Update Email Address" msgstr "" -#: views/subscription/manage.hbs:1 -msgid "Update your preferences" -msgstr "" - -#: views/subscription/manage.hbs:3 -msgid "want to change it?" -msgstr "" - -#: views/subscription/manage.hbs:6 -#: views/subscription/subscribe.hbs:6 -msgid "Download signature verification key" -msgstr "" - -#: views/subscription/manage.hbs:8 -#: views/subscription/subscribe.hbs:8 -msgid "Insert your GPG public key here to encrypt messages sent to your address" -msgstr "" - -#: views/subscription/manage.hbs:9 -#: views/subscription/subscribe.hbs:9 -msgid "optional" -msgstr "" - -#: views/subscription/manage.hbs:11 +#: views/subscription/partials/subscription-manage-form.hbs:1 +#: views/subscription/web-manage.mjml.hbs:2 msgid "Update Profile" msgstr "" -#: views/subscription/subscribe.hbs:2 -msgid "JavaScript must be enabled in order for the subscription form to work" -msgstr "" - -#: views/subscription/subscribe.hbs:11 +#: views/subscription/partials/subscription-subscribe-form.hbs:1 +#: views/subscription/web-subscribe.mjml.hbs:2 msgid "Subscribe to list" msgstr "" -#: views/subscription/subscribed.hbs:3 +#: views/subscription/web-confirm-notice.mjml.hbs:1 +msgid "Almost Finished" +msgstr "" + +#: views/subscription/web-confirm-notice.mjml.hbs:2 +msgid "" +"We need to confirm your email address. To complete the subscription " +"process, please click the link in the email we just sent you." +msgstr "" + +#: views/subscription/web-manage-address.mjml.hbs:1 +msgid "Update Your Email Address" +msgstr "" + +#: views/subscription/web-manage.mjml.hbs:1 +msgid "Update Your Preferences" +msgstr "" + +#: views/subscription/web-subscribe.mjml.hbs:1 +msgid "Subscribe to List" +msgstr "" + +#: views/subscription/web-subscribed.mjml.hbs:3 msgid "Thank you for subscribing!" msgstr "" -#: views/subscription/subscribed.hbs:4 -msgid "continue to our website" -msgstr "" - -#: views/subscription/unsubscribe-notice.hbs:1 +#: views/subscription/web-unsubscribe-notice.mjml.hbs:1 msgid "Unsubscribe Successful" msgstr "" -#: views/subscription/unsubscribe-notice.hbs:2 +#: views/subscription/web-unsubscribe-notice.mjml.hbs:2 msgid "You have been removed from:" msgstr "" -#: views/subscription/unsubscribe.hbs:2 +#: views/subscription/web-unsubscribe.mjml.hbs:2 msgid "Enter your email address to unsubscribe from:" msgstr "" -#: views/subscription/updated-notice.hbs:1 +#: views/subscription/web-updated-notice.mjml.hbs:1 msgid "Profile Updated" msgstr "" -#: views/subscription/updated-notice.hbs:2 +#: views/subscription/web-updated-notice.mjml.hbs:2 msgid "Your profile information has been updated." msgstr "" -#: views/templates/create.hbs:2 -#: views/templates/edit.hbs:2 -#: views/templates/templates.hbs:2 -#: views/templates/templates.hbs:4 -#: lib/tools.js:115 -msgid "Templates" -msgstr "" - #: views/templates/create.hbs:3 #: views/templates/create.hbs:4 #: views/templates/create.hbs:12 @@ -3011,45 +3166,45 @@ msgstr "" msgid "Bad status code %s" msgstr "" -#: lib/helpers.js:16 +#: lib/helpers.js:29 msgid "URL that points to the unsubscribe page" msgstr "" -#: lib/helpers.js:19 +#: lib/helpers.js:32 msgid "URL that points to the preferences page of the subscriber" msgstr "" -#: lib/helpers.js:22 +#: lib/helpers.js:35 msgid "URL to preview the message in a browser" msgstr "" -#: lib/helpers.js:28 +#: lib/helpers.js:41 #: lib/models/segments.js:31 msgid "First name" msgstr "" -#: lib/helpers.js:31 +#: lib/helpers.js:44 #: lib/models/segments.js:35 msgid "Last name" msgstr "" -#: lib/helpers.js:34 +#: lib/helpers.js:47 msgid "Full name (first and last name combined)" msgstr "" -#: lib/helpers.js:37 +#: lib/helpers.js:50 msgid "Unique ID that identifies the recipient" msgstr "" -#: lib/helpers.js:40 +#: lib/helpers.js:53 msgid "Unique ID that identifies the list used for this campaign" msgstr "" -#: lib/helpers.js:43 +#: lib/helpers.js:56 msgid "Unique ID that identifies current campaign" msgstr "" -#: lib/mailer.js:215 +#: lib/mailer.js:245 msgid "Invalid mail transport" msgstr "" @@ -3102,19 +3257,20 @@ msgstr "" #: lib/models/fields.js:53 #: lib/models/fields.js:98 #: lib/models/fields.js:123 +#: lib/models/forms.js:37 #: lib/models/lists.js:81 #: lib/models/lists.js:175 #: lib/models/lists.js:212 #: lib/models/segments.js:43 #: lib/models/segments.js:176 -#: lib/models/subscriptions.js:88 -#: lib/models/subscriptions.js:640 -#: lib/models/subscriptions.js:703 -#: lib/models/subscriptions.js:889 -#: lib/models/subscriptions.js:992 -#: lib/models/subscriptions.js:1046 -#: lib/models/subscriptions.js:1109 -#: lib/models/subscriptions.js:1152 +#: lib/models/subscriptions.js:89 +#: lib/models/subscriptions.js:661 +#: lib/models/subscriptions.js:724 +#: lib/models/subscriptions.js:910 +#: lib/models/subscriptions.js:1013 +#: lib/models/subscriptions.js:1067 +#: lib/models/subscriptions.js:1130 +#: lib/models/subscriptions.js:1173 msgid "Missing List ID" msgstr "" @@ -3153,6 +3309,22 @@ msgstr "" msgid "Provided List ID not found" msgstr "" +#: lib/models/forms.js:62 +#: lib/models/forms.js:88 +#: lib/models/forms.js:136 +#: lib/models/forms.js:183 +msgid "Missing Form ID" +msgstr "" + +#: lib/models/forms.js:96 +#: lib/models/forms.js:140 +msgid "Form Name must be set" +msgstr "" + +#: lib/models/forms.js:200 +msgid "Custom form not found" +msgstr "" + #: lib/models/links.js:328 #: routes/campaigns.js:541 #: routes/campaigns.js:590 @@ -3161,7 +3333,7 @@ msgid "Campaign not found" msgstr "" #: lib/models/links.js:336 -#: routes/lists.js:146 +#: routes/lists.js:162 #: services/sender.js:311 msgid "List not found" msgstr "" @@ -3270,48 +3442,48 @@ msgstr "" msgid "Selected rule not found" msgstr "" -#: lib/models/subscriptions.js:233 +#: lib/models/subscriptions.js:235 msgid "%s: Please Confirm Subscription" msgstr "" -#: lib/models/subscriptions.js:324 +#: lib/models/subscriptions.js:345 msgid "Could not save subscription" msgstr "" -#: lib/models/subscriptions.js:507 -#: lib/models/subscriptions.js:537 +#: lib/models/subscriptions.js:528 +#: lib/models/subscriptions.js:558 msgid "Missing Subbscription ID" msgstr "" -#: lib/models/subscriptions.js:565 +#: lib/models/subscriptions.js:586 msgid "Missing Subbscription email address" msgstr "" -#: lib/models/subscriptions.js:644 -#: lib/models/subscriptions.js:893 -#: lib/models/subscriptions.js:1156 +#: lib/models/subscriptions.js:665 +#: lib/models/subscriptions.js:914 +#: lib/models/subscriptions.js:1177 msgid "Missing subscription ID" msgstr "" -#: lib/models/subscriptions.js:707 +#: lib/models/subscriptions.js:728 msgid "Missing email address" msgstr "" -#: lib/models/subscriptions.js:996 -#: lib/models/subscriptions.js:1050 -#: lib/models/subscriptions.js:1086 +#: lib/models/subscriptions.js:1017 +#: lib/models/subscriptions.js:1071 +#: lib/models/subscriptions.js:1107 msgid "Missing Import ID" msgstr "" -#: lib/models/subscriptions.js:1178 +#: lib/models/subscriptions.js:1199 msgid "Unknown subscription ID" msgstr "" -#: lib/models/subscriptions.js:1183 +#: lib/models/subscriptions.js:1204 msgid "Nothing seems to be changed" msgstr "" -#: lib/models/subscriptions.js:1197 +#: lib/models/subscriptions.js:1218 msgid "This address is already registered by someone else" msgstr "" @@ -3453,30 +3625,30 @@ msgstr "" msgid "Incorrect username or password" msgstr "" -#: lib/tools.js:133 +#: lib/tools.js:136 msgid "Blocked email address \"%s\"" msgstr "" -#: lib/tools.js:142 +#: lib/tools.js:145 msgid "Invalid email address \"%s\"." msgstr "" -#: lib/tools.js:145 +#: lib/tools.js:148 msgid "MX record not found for domain" msgstr "" -#: lib/tools.js:148 +#: lib/tools.js:151 msgid "Address domain not found" msgstr "" -#: lib/tools.js:151 +#: lib/tools.js:154 msgid "Address domain name is required" msgstr "" #: routes/archive.js:31 #: routes/archive.js:43 #: routes/archive.js:55 -#: app.js:220 +#: app.js:225 msgid "Not Found" msgstr "" @@ -3493,8 +3665,9 @@ msgstr "" #: routes/campaigns.js:26 #: routes/editorapi.js:35 #: routes/fields.js:13 +#: routes/forms.js:16 #: routes/grapejs.js:13 -#: routes/lists.js:49 +#: routes/lists.js:50 #: routes/mosaico.js:14 #: routes/segments.js:13 #: routes/settings.js:23 @@ -3637,6 +3810,9 @@ msgstr "" #: routes/fields.js:28 #: routes/fields.js:64 #: routes/fields.js:118 +#: routes/forms.js:31 +#: routes/forms.js:63 +#: routes/forms.js:94 #: routes/segments.js:28 #: routes/segments.js:59 #: routes/segments.js:102 @@ -3673,6 +3849,94 @@ msgstr "" msgid "Could not delete specified field" msgstr "" +#: routes/forms.js:78 +msgid "Could not create custom form" +msgstr "" + +#: routes/forms.js:105 +msgid "Selected form not found" +msgstr "" + +#: routes/forms.js:141 +msgid "Layout" +msgstr "" + +#: routes/forms.js:146 +msgid "Form Input Style" +msgstr "" + +#: routes/forms.js:153 +msgid "Web - Subscribe" +msgstr "" + +#: routes/forms.js:157 +msgid "Web - Confirm Notice" +msgstr "" + +#: routes/forms.js:161 +msgid "Mail - Confirm Subscription (MJML)" +msgstr "" + +#: routes/forms.js:165 +msgid "Mail - Confirm Subscription (Text)" +msgstr "" + +#: routes/forms.js:169 +msgid "Web - Subscribed Notice" +msgstr "" + +#: routes/forms.js:173 +msgid "Mail - Subscription Confirmed (MJML)" +msgstr "" + +#: routes/forms.js:177 +msgid "Mail - Subscription Confirmed (Text)" +msgstr "" + +#: routes/forms.js:184 +msgid "Web - Manage Preferences" +msgstr "" + +#: routes/forms.js:188 +msgid "Web - Manage Address" +msgstr "" + +#: routes/forms.js:192 +msgid "Web - Updated Notice" +msgstr "" + +#: routes/forms.js:199 +msgid "Web - Unsubscribe" +msgstr "" + +#: routes/forms.js:203 +msgid "Web - Unsubscribe Notice" +msgstr "" + +#: routes/forms.js:207 +msgid "Mail - Unsubscribe Confirmed (MJML)" +msgstr "" + +#: routes/forms.js:211 +msgid "Mail - Unsubscribe Confirmed (Text)" +msgstr "" + +#: routes/forms.js:248 +msgid "Form settings updated" +msgstr "" + +#: routes/forms.js:250 +msgid "Form settings not updated" +msgstr "" + +#: routes/forms.js:266 +msgid "Custom form deleted" +msgstr "" + +#: routes/forms.js:268 +msgid "Could not delete specified form" +msgstr "" + #: routes/index.js:11 msgid "Self Hosted Newsletter App" msgstr "" @@ -3681,148 +3945,148 @@ msgstr "" msgid "Oops, we couldn't find a link for the URL you clicked" msgstr "" -#: routes/lists.js:90 +#: routes/lists.js:91 msgid "Could not create list" msgstr "" -#: routes/lists.js:93 +#: routes/lists.js:94 msgid "List created" msgstr "" -#: routes/lists.js:101 -#: routes/lists.js:236 -#: routes/lists.js:301 -#: routes/lists.js:340 -#: routes/lists.js:409 -#: routes/lists.js:434 -#: routes/lists.js:479 -#: routes/lists.js:501 -#: routes/lists.js:530 -#: routes/lists.js:609 -#: routes/lists.js:666 -#: routes/lists.js:693 +#: routes/lists.js:102 +#: routes/lists.js:252 +#: routes/lists.js:317 +#: routes/lists.js:356 +#: routes/lists.js:425 +#: routes/lists.js:450 +#: routes/lists.js:495 +#: routes/lists.js:517 +#: routes/lists.js:546 +#: routes/lists.js:625 +#: routes/lists.js:682 +#: routes/lists.js:709 msgid "Could not find list with specified ID" msgstr "" -#: routes/lists.js:115 +#: routes/lists.js:129 msgid "List settings updated" msgstr "" -#: routes/lists.js:117 +#: routes/lists.js:131 msgid "List settings not updated" msgstr "" -#: routes/lists.js:133 +#: routes/lists.js:149 msgid "List deleted" msgstr "" -#: routes/lists.js:135 +#: routes/lists.js:151 msgid "Could not delete specified list" msgstr "" -#: routes/lists.js:171 +#: routes/lists.js:187 msgid "Unknown" msgstr "" -#: routes/lists.js:171 +#: routes/lists.js:187 msgid "Complained" msgstr "" -#: routes/lists.js:202 +#: routes/lists.js:218 msgid "Invalid key" msgstr "" -#: routes/lists.js:204 +#: routes/lists.js:220 msgid "Expired key" msgstr "" -#: routes/lists.js:206 +#: routes/lists.js:222 msgid "Revoked key" msgstr "" -#: routes/lists.js:256 +#: routes/lists.js:272 msgid "Initializing" msgstr "" -#: routes/lists.js:259 +#: routes/lists.js:275 msgid "Initialized" msgstr "" -#: routes/lists.js:262 +#: routes/lists.js:278 msgid "Importing" msgstr "" -#: routes/lists.js:268 +#: routes/lists.js:284 msgid "Errored" msgstr "" -#: routes/lists.js:346 -#: routes/lists.js:415 -#: routes/lists.js:440 +#: routes/lists.js:362 +#: routes/lists.js:431 +#: routes/lists.js:456 msgid "Could not find subscriber with specified ID" msgstr "" -#: routes/lists.js:392 +#: routes/lists.js:408 msgid "Could not add subscription" msgstr "" -#: routes/lists.js:397 +#: routes/lists.js:413 msgid "%s was successfully added to your list" msgstr "" -#: routes/lists.js:399 +#: routes/lists.js:415 msgid "%s was not added to your list" msgstr "" -#: routes/lists.js:421 +#: routes/lists.js:437 msgid "Could not unsubscribe user" msgstr "" -#: routes/lists.js:424 +#: routes/lists.js:440 msgid "%s was successfully unsubscribed from your list" msgstr "" -#: routes/lists.js:444 +#: routes/lists.js:460 msgid "%s was successfully removed from your list" msgstr "" -#: routes/lists.js:456 +#: routes/lists.js:472 msgid "Another subscriber with email address %s already exists" msgstr "" -#: routes/lists.js:463 +#: routes/lists.js:479 msgid "Subscription settings updated" msgstr "" -#: routes/lists.js:465 +#: routes/lists.js:481 msgid "Subscription settings not updated" msgstr "" -#: routes/lists.js:507 -#: routes/lists.js:615 -#: routes/lists.js:651 -#: routes/lists.js:679 -#: routes/lists.js:699 +#: routes/lists.js:523 +#: routes/lists.js:631 +#: routes/lists.js:667 +#: routes/lists.js:695 +#: routes/lists.js:715 msgid "Could not find import data with specified ID" msgstr "" -#: routes/lists.js:538 +#: routes/lists.js:554 msgid "Could not process CSV" msgstr "" -#: routes/lists.js:547 +#: routes/lists.js:563 msgid "Could not create importer" msgstr "" -#: routes/lists.js:598 +#: routes/lists.js:614 msgid "Empty file" msgstr "" -#: routes/lists.js:655 +#: routes/lists.js:671 msgid "Import started" msgstr "" -#: routes/lists.js:683 +#: routes/lists.js:699 msgid "Import restarted" msgstr "" @@ -3954,50 +4218,53 @@ msgstr "" msgid "Mailer settings verified, ready to send some mail!" msgstr "" -#: routes/subscription.js:22 +#: routes/subscription.js:25 msgid "Selected subscription not found" msgstr "" -#: routes/subscription.js:32 -#: routes/subscription.js:103 -#: routes/subscription.js:141 -#: routes/subscription.js:166 -#: routes/subscription.js:191 -#: routes/subscription.js:232 -#: routes/subscription.js:270 -#: routes/subscription.js:317 -#: routes/subscription.js:339 -#: routes/subscription.js:368 -#: routes/subscription.js:392 -#: routes/subscription.js:424 +#: routes/subscription.js:35 +#: routes/subscription.js:155 +#: routes/subscription.js:222 +#: routes/subscription.js:275 +#: routes/subscription.js:327 +#: routes/subscription.js:396 +#: routes/subscription.js:434 +#: routes/subscription.js:510 +#: routes/subscription.js:532 +#: routes/subscription.js:592 +#: routes/subscription.js:616 +#: routes/subscription.js:681 msgid "Selected list not found" msgstr "" -#: routes/subscription.js:78 -#: routes/subscription.js:472 +#: routes/subscription.js:109 msgid "%s: Subscription Confirmed" msgstr "" -#: routes/subscription.js:217 +#: routes/subscription.js:381 msgid "Email address not set" msgstr "" -#: routes/subscription.js:255 +#: routes/subscription.js:419 msgid "Could not store confirmation data" msgstr "" -#: routes/subscription.js:284 -#: routes/subscription.js:349 -#: routes/subscription.js:402 +#: routes/subscription.js:448 +#: routes/subscription.js:547 +#: routes/subscription.js:631 msgid "Subscription not found from this list" msgstr "" -#: routes/subscription.js:383 +#: routes/subscription.js:607 msgid "Email address updated, check your mailbox for verification instructions" msgstr "" -#: routes/subscription.js:499 -#: routes/subscription.js:515 +#: routes/subscription.js:730 +msgid "%s: Unsubscribe Confirmed" +msgstr "" + +#: routes/subscription.js:777 +#: routes/subscription.js:793 msgid "Public key is not set" msgstr "" diff --git a/lib/dbcheck.js b/lib/dbcheck.js index 001723a1..f3165b5c 100644 --- a/lib/dbcheck.js +++ b/lib/dbcheck.js @@ -204,14 +204,57 @@ function applyUpdate(update, callback) { }); } -module.exports = callback => { - runUpdates(err => { +function verifyInnodbFileFormat(callback) { + db.getConnection((err, connection) => { if (err) { return callback(err); } - db.end(() => { - log.info('sql', 'Database check completed'); - return callback(null, true); + + connection.query('SELECT @@GLOBAL.innodb_file_format as value', [], (err, rows) => { + if (err) { + return callback(err); + } + + let format = rows && rows[0] && rows[0].value; + + if (!format || format.toLowerCase() !== 'barracuda') { + + log.warn('sql', 'Temporary setting innodb_file_format to barracuda'); + log.warn('sql', 'Please modify the [mysqld] section of your my.cnf'); + log.warn('sql', '------------------------------'); + log.warn('sql', '[mysqld]'); + log.warn('sql', 'innodb_file_per_table = 1'); + log.warn('sql', 'innodb_file_format = barracuda'); + log.warn('sql', '------------------------------'); + + connection.query('SET GLOBAL innodb_file_per_table = 1; SET GLOBAL innodb_file_format = Barracuda', [], err => { + connection.release(); + if (err) { + return callback(err); + } + + callback(null); + }); + } else { + return callback(null); + } + }); + }); +} + +module.exports = callback => { + verifyInnodbFileFormat(err => { + if (err) { + return callback(err); + } + runUpdates(err => { + if (err) { + return callback(err); + } + db.end(() => { + log.info('sql', 'Database check completed'); + return callback(null, true); + }); }); }); }; diff --git a/lib/helpers.js b/lib/helpers.js index 2366947f..62545670 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,12 +1,25 @@ 'use strict'; +let path = require('path'); +let fs = require('fs'); +let tools = require('./tools'); let lists = require('./models/lists'); let fields = require('./models/fields'); +let forms = require('./models/forms'); let _ = require('./translate')._; +let objectHash = require('object-hash'); +let mjml = require('mjml'); +let mjmlTemplates = new Map(); +let hbs = require('hbs'); module.exports = { getDefaultMergeTags, - getListMergeTags + getListMergeTags, + captureFlashMessages, + injectCustomFormData, + injectCustomFormTemplates, + filterCustomFields, + getMjmlTemplate }; function getDefaultMergeTags(callback) { @@ -73,3 +86,166 @@ function getListMergeTags(listId, callback) { }); }); } + +function filterCustomFields(customFieldsIn = [], fieldIds = [], method = 'include') { + let customFields = customFieldsIn.slice(); + fieldIds = typeof fieldIds === 'string' ? fieldIds.split(',') : fieldIds; + + customFields.unshift({ + id: 'email', + name: 'Email Address', + type: 'Email', + typeSubsciptionEmail: true + }, { + id: 'firstname', + name: 'First Name', + type: 'Text', + typeFirstName: true + }, { + id: 'lastname', + name: 'Last Name', + type: 'Text', + typeLastName: true + }); + + let filtered = []; + + if (method === 'include') { + fieldIds.forEach(id => { + let field = customFields.find(f => f.id.toString() === id); + field && filtered.push(field); + }); + } else { + customFields.forEach(field => { + !fieldIds.includes(field.id.toString()) && filtered.push(field); + }); + } + + return filtered; +} + +function injectCustomFormData(customFormId, viewPath, data, callback) { + + let injectDefaultData = data => { + data.customFields = filterCustomFields(data.customFields, [], 'exclude'); + data.formInputStyle = '@import url(/subscription/form-input-style.css);'; + return data; + }; + + if (Number(customFormId) < 1) { + return callback(null, injectDefaultData(data)); + } + + forms.get(customFormId, (err, form) => { + if (err) { + return callback(null, injectDefaultData(data)); + } + + let view = viewPath.split('/')[1]; + + if (view === 'web-subscribe') { + data.customFields = form.fieldsShownOnSubscribe + ? filterCustomFields(data.customFields, form.fieldsShownOnSubscribe) + : filterCustomFields(data.customFields, [], 'exclude'); + } else if (view === 'web-manage') { + data.customFields = form.fieldsShownOnManage + ? filterCustomFields(data.customFields, form.fieldsShownOnManage) + : filterCustomFields(data.customFields, [], 'exclude'); + } + + let key = tools.fromDbKey(view); + data.template.template = form[key] || data.template.template; + data.template.layout = form.layout || data.template.layout; + + data.formInputStyle = form.formInputStyle || '@import url(/subscription/form-input-style.css);'; + + callback(null, data); + }); +} + +function injectCustomFormTemplates(customFormId, templates, callback) { + if (Number(customFormId) < 1) { + return callback(null, templates); + } + + forms.get(customFormId, (err, form) => { + if (err) { + return callback(null, templates); + } + + let lookUp = name => { + let key = tools.fromDbKey( + /subscription\/([^.]*)/.exec(name)[1] + ); + return form[key] || name; + }; + + Object.keys(templates).forEach(key => { + let value = templates[key]; + + if (typeof value === 'string') { + templates[key] = lookUp(value); + } + if (typeof value === 'object' && value.template) { + templates[key].template = lookUp(value.template); + } + if (typeof value === 'object' && value.layout) { + templates[key].layout = lookUp(value.layout); + } + }); + + callback(null, templates); + }); +} + +function getMjmlTemplate(template, callback) { + if (!template) { + return callback(null, false); + } + + let key = (typeof template === 'object') ? objectHash(template) : template; + + if (mjmlTemplates.has(key)) { + return callback(null, mjmlTemplates.get(key)); + } + + let done = source => { + let compiled; + try { + compiled = mjml.mjml2html(source); + } catch (err) { + return callback(err); + } + if (compiled.errors.length) { + return callback(compiled.errors[0].message || compiled.errors[0]); + } + let renderer = hbs.handlebars.compile(compiled.html); + mjmlTemplates.set(key, renderer); + callback(null, renderer); + }; + + if (typeof template === 'object') { + tools.mergeTemplateIntoLayout(template.template, template.layout, (err, source) => { + if (err) { + return callback(err); + } + done(source); + }); + } else { + fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8', (err, source) => { + if (err) { + return callback(err); + } + done(source); + }); + } +} + +function captureFlashMessages(req, res, callback) { + res.render('subscription/capture-flash-messages', { layout: null }, (err, flash) => { + if (err) { + return callback(err); + } + callback(null, flash); + }); +} diff --git a/lib/mailer.js b/lib/mailer.js index 83f55e5a..8e84bf7d 100644 --- a/lib/mailer.js +++ b/lib/mailer.js @@ -13,6 +13,8 @@ let path = require('path'); let templates = new Map(); let htmlToText = require('html-to-text'); let aws = require('aws-sdk'); +let objectHash = require('object-hash'); +let mjml = require('mjml'); let _ = require('./translate')._; let util = require('util'); @@ -124,18 +126,46 @@ function getTemplate(template, callback) { return callback(null, false); } - if (templates.has(template)) { - return callback(null, templates.get(template)); + let key = (typeof template === 'object') ? objectHash(template) : template; + + if (templates.has(key)) { + return callback(null, templates.get(key)); } - fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8', (err, source) => { - if (err) { - return callback(err); + let done = (source, isMjml = false) => { + if (isMjml) { + let compiled; + try { + compiled = mjml.mjml2html(source); + } catch (err) { + return callback(err); + } + if (compiled.errors.length) { + return callback(compiled.errors[0].message || compiled.errors[0]); + } + source = compiled.html; } let renderer = Handlebars.compile(source); - templates.set(template, renderer); - return callback(null, renderer); - }); + templates.set(key, renderer); + callback(null, renderer); + }; + + if (typeof template === 'object') { + tools.mergeTemplateIntoLayout(template.template, template.layout, (err, source) => { + if (err) { + return callback(err); + } + let isMjml = template.type === 'mjml'; + done(source, isMjml); + }); + } else { + fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8', (err, source) => { + if (err) { + return callback(err); + } + done(source); + }); + } } function createMailer(callback) { diff --git a/lib/models/fields.js b/lib/models/fields.js index 2e10b322..8fe5d6e8 100644 --- a/lib/models/fields.js +++ b/lib/models/fields.js @@ -405,6 +405,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => { case 'longtext': { let item = { + id: field.id, type: field.type, name: field.name, column: field.column, @@ -434,6 +435,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => { } let item = { + id: field.id, type: field.type, name: field.name, column: field.column, @@ -449,6 +451,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => { case 'number': { let item = { + id: field.id, type: field.type, name: field.name, column: field.column, @@ -466,6 +469,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => { case 'checkbox': { let item = { + id: field.id, type: field.type, name: field.name, visible: !!field.visible, @@ -556,6 +560,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => { } let item = { + id: field.id, type: field.type, name: field.name, column: field.column, diff --git a/lib/models/forms.js b/lib/models/forms.js new file mode 100644 index 00000000..a47135cf --- /dev/null +++ b/lib/models/forms.js @@ -0,0 +1,283 @@ +'use strict'; + +let db = require('../db'); +let fs = require('fs'); +let path = require('path'); +let tools = require('../tools'); +let mjml = require('mjml'); +let _ = require('../translate')._; + +let allowedKeys = [ + 'name', + 'description', + 'fields_shown_on_subscribe', + 'fields_shown_on_manage', + 'layout', + 'form_input_style', + 'mail_confirm_html', + 'mail_confirm_text', + 'mail_subscription_confirmed_html', + 'mail_subscription_confirmed_text', + 'mail_unsubscribe_confirmed_html', + 'mail_unsubscribe_confirmed_text', + 'web_confirm_notice', + 'web_manage_address', + 'web_manage', + 'web_subscribe', + 'web_subscribed', + 'web_unsubscribe_notice', + 'web_unsubscribe', + 'web_updated_notice' +]; + +module.exports.list = (listId, callback) => { + listId = Number(listId) || 0; + + if (listId < 1) { + return callback(new Error(_('Missing List ID'))); + } + + db.getConnection((err, connection) => { + if (err) { + return callback(err); + } + + let query = 'SELECT * FROM custom_forms WHERE list=? ORDER BY id'; + connection.query(query, [listId], (err, rows) => { + connection.release(); + if (err) { + return callback(err); + } + + let formList = rows && rows.map(row => tools.convertKeys(row)) || []; + return callback(null, formList); + }); + }); +}; + +module.exports.get = (id, callback) => { + id = Number(id) || 0; + + if (id < 1) { + return callback(new Error(_('Missing Form ID'))); + } + + db.getConnection((err, connection) => { + if (err) { + return callback(err); + } + + let query = 'SELECT * FROM custom_forms WHERE id=? LIMIT 1'; + connection.query(query, [id], (err, rows) => { + connection.release(); + if (err) { + return callback(err); + } + + let form = rows && rows[0] && tools.convertKeys(rows[0]) || false; + return callback(null, form); + }); + }); +}; + + +module.exports.create = (listId, form, callback) => { + listId = Number(listId) || 0; + + if (listId < 1) { + return callback(new Error(_('Missing Form ID'))); + } + + form = tools.convertKeys(form); + form = setDefaultValues(form); + form.name = (form.name || '').toString().trim(); + + if (!form.name) { + return callback(new Error(_('Form Name must be set'))); + } + + let keys = ['list']; + let values = [listId]; + + Object.keys(form).forEach(key => { + let value = form[key].trim(); + key = tools.toDbKey(key); + if (allowedKeys.indexOf(key) >= 0) { + keys.push(key); + values.push(value); + } + }); + + db.getConnection((err, connection) => { + if (err) { + return callback(err); + } + + let query = 'INSERT INTO custom_forms (' + keys.join(', ') + ') VALUES (' + values.map(() => '?').join(',') + ')'; + connection.query(query, values, (err, result) => { + connection.release(); + if (err) { + return callback(err); + } + + let formId = result && result.insertId || false; + return callback(null, formId); + }); + }); +}; + +module.exports.update = (id, updates, callback) => { + updates = updates || {}; + id = Number(id) || 0; + + updates = tools.convertKeys(updates); + + if (id < 1) { + return callback(new Error(_('Missing Form ID'))); + } + + if (!(updates.name || '').toString().trim()) { + return callback(new Error(_('Form Name must be set'))); + } + + let keys = []; + let values = []; + + Object.keys(updates).forEach(key => { + let value = typeof updates[key] === 'string' ? updates[key].trim() : updates[key]; + key = tools.toDbKey(key); + if (allowedKeys.indexOf(key) >= 0) { + keys.push(key); + values.push(value); + } + }); + + db.getConnection((err, connection) => { + if (err) { + return callback(err); + } + + values.push(id); + + connection.query('UPDATE custom_forms SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => { + connection.release(); + if (err) { + return callback(err); + } + + // Save then validate, as otherwise their work get's lost ... + err = testForMjmlErrors(keys, values); + if (err) { + return callback(err); + } + + return callback(null, result && result.affectedRows || false); + }); + }); +}; + +module.exports.delete = (formId, callback) => { + formId = Number(formId) || 0; + + if (formId < 1) { + return callback(new Error(_('Missing Form ID'))); + } + + db.getConnection((err, connection) => { + if (err) { + return callback(err); + } + + let query = 'SELECT * FROM custom_forms WHERE id=? LIMIT 1'; + connection.query(query, [formId], (err, rows) => { + if (err) { + connection.release(); + return callback(err); + } + + if (!rows || !rows.length) { + connection.release(); + return callback(new Error(_('Custom form not found'))); + } + + connection.query('DELETE FROM custom_forms WHERE id=? LIMIT 1', [formId], err => { + if (err) { + connection.release(); + return callback(err); + } + return callback(null, true); + }); + }); + }); +}; + +function setDefaultValues(form) { + let getContents = fileName => { + try { + let basePath = path.join(__dirname, '..', '..'); + let template = fs.readFileSync(path.join(basePath, fileName), 'utf8'); + return template.replace(/\{\{#translate\}\}(.*?)\{\{\/translate\}\}/g, (m, s) => _(s)); + } catch (err) { + return false; + } + }; + + allowedKeys.forEach(key => { + let modelKey = tools.fromDbKey(key); + let base = 'views/subscription/' + key.replace(/_/g, '-'); + + if (key.startsWith('mail') || key.startsWith('web')) { + form[modelKey] = getContents(base + '.mjml.hbs') || getContents(base + '.hbs') || ''; + } + }); + + form.layout = getContents('views/subscription/layout.mjml.hbs') || ''; + form.formInputStyle = getContents('public/subscription/form-input-style.css') || '@import url(/subscription/form-input-style.css);'; + + return form; +} + + +function testForMjmlErrors(keys, values) { + let testLayout = '{{{body}}}'; + let isInvalidMjml = (template, layout = testLayout) => { + let source = layout.replace(/\{\{\{body\}\}\}/g, template); + let compiled; + try { + compiled = mjml.mjml2html(source); + } catch (err) { + return err; + } + if (compiled.errors.length) { + return compiled.errors[0].message || compiled.errors[0]; + } + return null; + }; + + let errors = []; + + keys.forEach((key, index) => { + if (key.startsWith('mail_') || key.startsWith('web_')) { + let template = values[index]; + let err = isInvalidMjml(template); + err && errors.push(key + ': ' + (err.message || err)); + if (key === 'mail_confirm_html' && !template.includes('{{confirmUrl}}')) { + errors.push(key + ': Missing {{confirmUrl}}'); + } + } else if (key === 'layout') { + let layout = values[index]; + let err = isInvalidMjml('', layout); + err && errors.push('layout: ' + (err.message || err)); + !layout.includes('{{{body}}}') && errors.push('layout: {{{body}}} not found'); + } + }); + + if (errors.length) { + errors.forEach((err, index) => { + errors[index] = (index + 1) + ') ' + err; + }); + return 'Please Fix These Errors:\n\n' + errors.join('\n'); + } + + return null; +} diff --git a/lib/models/lists.js b/lib/models/lists.js index 4d58952c..f17c4886 100644 --- a/lib/models/lists.js +++ b/lib/models/lists.js @@ -6,7 +6,7 @@ let shortid = require('shortid'); let segments = require('./segments'); let _ = require('../translate')._; -let allowedKeys = ['description']; +let allowedKeys = ['description', 'default_form']; module.exports.list = (start, limit, callback) => { db.getConnection((err, connection) => { diff --git a/lib/models/subscriptions.js b/lib/models/subscriptions.js index 65731dac..a966e96f 100644 --- a/lib/models/subscriptions.js +++ b/lib/models/subscriptions.js @@ -3,6 +3,7 @@ let db = require('../db'); let shortid = require('shortid'); let tools = require('../tools'); +let helpers = require('../helpers'); let fields = require('./fields'); let geoip = require('geoip-ultralight'); let segments = require('./segments'); @@ -210,7 +211,7 @@ module.exports.addConfirmation = (list, email, optInIp, data, callback) => { } }); - settings.list(['defaultHomepage', 'defaultFrom', 'defaultAddress', 'serviceUrl'], (err, configItems) => { + settings.list(['defaultHomepage', 'defaultFrom', 'defaultAddress', 'defaultPostaddress', 'serviceUrl'], (err, configItems) => { if (err) { return callback(err); } @@ -221,35 +222,55 @@ module.exports.addConfirmation = (list, email, optInIp, data, callback) => { return; } - mailer.sendMail({ - from: { - name: configItems.defaultFrom, - address: configItems.defaultAddress - }, - to: { - name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '), - address: email - }, - subject: util.format(_('%s: Please Confirm Subscription'), list.name), - encryptionKeys - }, { - html: 'emails/confirm-html.hbs', - text: 'emails/confirm-text.hbs', - data: { - title: list.name, - contactAddress: configItems.defaultAddress, - confirmUrl: urllib.resolve(configItems.serviceUrl, '/subscription/subscribe/' + cid) - } - }, err => { + let sendMail = (html, text) => { + mailer.sendMail({ + from: { + name: configItems.defaultFrom, + address: configItems.defaultAddress + }, + to: { + name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '), + address: email + }, + subject: util.format(_('%s: Please Confirm Subscription'), list.name), + encryptionKeys + }, { + html, + text, + data: { + title: list.name, + contactAddress: configItems.defaultAddress, + defaultPostaddress: configItems.defaultPostaddress, + confirmUrl: urllib.resolve(configItems.serviceUrl, '/subscription/subscribe/' + cid) + } + }, err => { + if (err) { + log.error('Subscription', err.stack); + } + }); + }; + + let text = { + template: 'subscription/mail-confirm-text.hbs' + }; + + let html = { + template: 'subscription/mail-confirm-html.mjml.hbs', + layout: 'subscription/layout.mjml.hbs', + type: 'mjml' + }; + + helpers.injectCustomFormTemplates(list.defaultForm, { text, html }, (err, tmpl) => { if (err) { - log.error('Subscription', err.stack); + return sendMail(html, text); } + + sendMail(tmpl.html, tmpl.text); }); }); return callback(null, cid); }); }); - }); }); }); diff --git a/lib/tools.js b/lib/tools.js index 4fc8b396..9e8591cc 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -8,6 +8,8 @@ let juice = require('juice'); let jsdom = require('jsdom'); let _ = require('./translate')._; let util = require('util'); +let fs = require('fs'); +let path = require('path'); let blockedUsers = ['abuse', 'admin', 'billing', 'compliance', 'devnull', 'dns', 'ftp', 'hostmaster', 'inoc', 'ispfeedback', 'ispsupport', 'listrequest', 'list', 'maildaemon', 'noc', 'noreply', 'noreply', 'null', 'phish', 'phishing', 'postmaster', 'privacy', 'registrar', 'root', 'security', 'spam', 'support', 'sysadmin', 'tech', 'undisclosedrecipients', 'unsubscribe', 'usenet', 'uucp', 'webmaster', 'www']; @@ -22,6 +24,7 @@ module.exports = { formatMessage, getMessageLinks, prepareHtml, + mergeTemplateIntoLayout, workers: new Set() }; @@ -224,3 +227,51 @@ function prepareHtml(html, callback) { return callback(null, juice(preparedHtml)); }); } + +// TODO Simplify! +function mergeTemplateIntoLayout(template, layout, callback) { + + layout = layout || '{{{body}}}'; + + let readFile = (relPath, callback) => { + fs.readFile(path.join(__dirname, '..', 'views', relPath), 'utf-8', (err, source) => { + if (err) { + return callback(err); + } + callback(null, source); + }); + }; + + let done = (template, layout) => { + let source = layout.replace(/\{\{\{body\}\}\}/g, template); + return callback(null, source); + }; + + if (layout.endsWith('.hbs')) { + readFile(layout, (err, layout) => { + if (err) { + return callback(err); + } + // Please dont end your custom messages with .hbs ... + if (template.endsWith('.hbs')) { + readFile(template, (err, template) => { + if (err) { + return callback(err); + } + return done(template, layout); + }); + } else { + return done(template, layout); + } + }); + } else if (template.endsWith('.hbs')) { + readFile(template, (err, template) => { + if (err) { + return callback(err); + } + return done(template, layout); + }); + } else { + return done(template, layout); + } +} diff --git a/meta.json b/meta.json index 731dc8bf..7bb9c8c4 100644 --- a/meta.json +++ b/meta.json @@ -1,3 +1,3 @@ { - "schemaVersion": 21 + "schemaVersion": 22 } diff --git a/package.json b/package.json index dab0933f..56395776 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "libmime": "^3.1.0", "marked": "^0.3.6", "memory-cache": "^0.1.6", + "mjml": "^3.2.2", "mkdirp": "^0.5.1", "moment-timezone": "^0.5.11", "morgan": "^1.8.1", @@ -80,6 +81,7 @@ "nodemailer": "^3.1.5", "nodemailer-openpgp": "^1.0.2", "npmlog": "^4.0.2", + "object-hash": "^1.1.7", "openpgp": "^2.4.0", "passport": "^0.3.2", "passport-local": "^1.0.0", diff --git a/public/ace/mode-css.js b/public/ace/mode-css.js new file mode 100644 index 00000000..84cd16c6 --- /dev/null +++ b/public/ace/mode-css.js @@ -0,0 +1 @@ +ace.define("ace/mode/css_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/lang"),s=e("./text_highlight_rules").TextHighlightRules,o=t.supportType="align-content|align-items|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-shadow|box-sizing|caption-side|clear|clip|color|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|content|counter-increment|counter-reset|cursor|direction|display|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|font|font-family|font-size|font-size-adjust|font-stretch|font-style|font-variant|font-weight|hanging-punctuation|height|justify-content|left|letter-spacing|line-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|max-height|max-width|min-height|min-width|nav-down|nav-index|nav-left|nav-right|nav-up|opacity|order|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page-break-after|page-break-before|page-break-inside|perspective|perspective-origin|position|quotes|resize|right|tab-size|table-layout|text-align|text-align-last|text-decoration|text-decoration-color|text-decoration-line|text-decoration-style|text-indent|text-justify|text-overflow|text-shadow|text-transform|top|transform|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|vertical-align|visibility|white-space|width|word-break|word-spacing|word-wrap|z-index",u=t.supportFunction="rgb|rgba|url|attr|counter|counters",a=t.supportConstant="absolute|after-edge|after|all-scroll|all|alphabetic|always|antialiased|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default|disabled|disc|disregard-shifts|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ease-in|ease-in-out|ease-out|ease|ellipsis|end|exclude-ruby|fill|fixed|georgian|glyphs|grid-height|groove|hand|hanging|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|include-ruby|inherit|initial|inline-block|inline-box|inline-line-height|inline-table|inline|inset|inside|inter-ideograph|inter-word|invert|italic|justify|katakana-iroha|katakana|keep-all|last|left|lighter|line-edge|line-through|line|linear|list-item|local|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|mathematical|max-height|max-size|medium|menu|message-box|middle|move|n-resize|ne-resize|newspaper|no-change|no-close-quote|no-drop|no-open-quote|no-repeat|none|normal|not-allowed|nowrap|nw-resize|oblique|open-quote|outset|outside|overline|padding-box|page|pointer|pre-line|pre-wrap|pre|preserve-3d|progress|relative|repeat-x|repeat-y|repeat|replaced|reset-size|ridge|right|round|row-resize|rtl|s-resize|scroll|se-resize|separate|slice|small-caps|small-caption|solid|space|square|start|static|status-bar|step-end|step-start|steps|stretch|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tb-rl|text-after-edge|text-before-edge|text-bottom|text-size|text-top|text|thick|thin|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|use-script|vertical-ideographic|vertical-text|visible|w-resize|wait|whitespace|z-index|zero",f=t.supportConstantColor="aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow",l=t.supportConstantFonts="arial|century|comic|courier|cursive|fantasy|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace",c=t.numRe="\\-?(?:(?:[0-9]+)|(?:[0-9]*\\.[0-9]+))",h=t.pseudoElements="(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b",p=t.pseudoClasses="(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b",d=function(){var e=this.createKeywordMapper({"support.function":u,"support.constant":a,"support.type":o,"support.constant.color":f,"support.constant.fonts":l},"text",!0);this.$rules={start:[{token:"comment",regex:"\\/\\*",push:"comment"},{token:"paren.lparen",regex:"\\{",push:"ruleset"},{token:"string",regex:"@.*?{",push:"media"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant",regex:"[a-z0-9-_]+"},{caseInsensitive:!0}],media:[{token:"comment",regex:"\\/\\*",push:"comment"},{token:"paren.lparen",regex:"\\{",push:"ruleset"},{token:"string",regex:"\\}",next:"pop"},{token:"keyword",regex:"#[a-z0-9-_]+"},{token:"variable",regex:"\\.[a-z0-9-_]+"},{token:"string",regex:":[a-z0-9-_]+"},{token:"constant",regex:"[a-z0-9-_]+"},{caseInsensitive:!0}],comment:[{token:"comment",regex:"\\*\\/",next:"pop"},{defaultToken:"comment"}],ruleset:[{token:"paren.rparen",regex:"\\}",next:"pop"},{token:"comment",regex:"\\/\\*",push:"comment"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:["constant.numeric","keyword"],regex:"("+c+")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vm|vw|%)"},{token:"constant.numeric",regex:c},{token:"constant.numeric",regex:"#[a-f0-9]{6}"},{token:"constant.numeric",regex:"#[a-f0-9]{3}"},{token:["punctuation","entity.other.attribute-name.pseudo-element.css"],regex:h},{token:["punctuation","entity.other.attribute-name.pseudo-class.css"],regex:p},{token:["support.function","string","support.function"],regex:"(url\\()(.*)(\\))"},{token:e,regex:"\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"},{caseInsensitive:!0}]},this.normalizeRules()};r.inherits(d,s),t.CssHighlightRules=d}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/css_completions",["require","exports","module"],function(e,t,n){"use strict";var r={background:{"#$0":1},"background-color":{"#$0":1,transparent:1,fixed:1},"background-image":{"url('/$0')":1},"background-repeat":{repeat:1,"repeat-x":1,"repeat-y":1,"no-repeat":1,inherit:1},"background-position":{bottom:2,center:2,left:2,right:2,top:2,inherit:2},"background-attachment":{scroll:1,fixed:1},"background-size":{cover:1,contain:1},"background-clip":{"border-box":1,"padding-box":1,"content-box":1},"background-origin":{"border-box":1,"padding-box":1,"content-box":1},border:{"solid $0":1,"dashed $0":1,"dotted $0":1,"#$0":1},"border-color":{"#$0":1},"border-style":{solid:2,dashed:2,dotted:2,"double":2,groove:2,hidden:2,inherit:2,inset:2,none:2,outset:2,ridged:2},"border-collapse":{collapse:1,separate:1},bottom:{px:1,em:1,"%":1},clear:{left:1,right:1,both:1,none:1},color:{"#$0":1,"rgb(#$00,0,0)":1},cursor:{"default":1,pointer:1,move:1,text:1,wait:1,help:1,progress:1,"n-resize":1,"ne-resize":1,"e-resize":1,"se-resize":1,"s-resize":1,"sw-resize":1,"w-resize":1,"nw-resize":1},display:{none:1,block:1,inline:1,"inline-block":1,"table-cell":1},"empty-cells":{show:1,hide:1},"float":{left:1,right:1,none:1},"font-family":{Arial:2,"Comic Sans MS":2,Consolas:2,"Courier New":2,Courier:2,Georgia:2,Monospace:2,"Sans-Serif":2,"Segoe UI":2,Tahoma:2,"Times New Roman":2,"Trebuchet MS":2,Verdana:1},"font-size":{px:1,em:1,"%":1},"font-weight":{bold:1,normal:1},"font-style":{italic:1,normal:1},"font-variant":{normal:1,"small-caps":1},height:{px:1,em:1,"%":1},left:{px:1,em:1,"%":1},"letter-spacing":{normal:1},"line-height":{normal:1},"list-style-type":{none:1,disc:1,circle:1,square:1,decimal:1,"decimal-leading-zero":1,"lower-roman":1,"upper-roman":1,"lower-greek":1,"lower-latin":1,"upper-latin":1,georgian:1,"lower-alpha":1,"upper-alpha":1},margin:{px:1,em:1,"%":1},"margin-right":{px:1,em:1,"%":1},"margin-left":{px:1,em:1,"%":1},"margin-top":{px:1,em:1,"%":1},"margin-bottom":{px:1,em:1,"%":1},"max-height":{px:1,em:1,"%":1},"max-width":{px:1,em:1,"%":1},"min-height":{px:1,em:1,"%":1},"min-width":{px:1,em:1,"%":1},overflow:{hidden:1,visible:1,auto:1,scroll:1},"overflow-x":{hidden:1,visible:1,auto:1,scroll:1},"overflow-y":{hidden:1,visible:1,auto:1,scroll:1},padding:{px:1,em:1,"%":1},"padding-top":{px:1,em:1,"%":1},"padding-right":{px:1,em:1,"%":1},"padding-bottom":{px:1,em:1,"%":1},"padding-left":{px:1,em:1,"%":1},"page-break-after":{auto:1,always:1,avoid:1,left:1,right:1},"page-break-before":{auto:1,always:1,avoid:1,left:1,right:1},position:{absolute:1,relative:1,fixed:1,"static":1},right:{px:1,em:1,"%":1},"table-layout":{fixed:1,auto:1},"text-decoration":{none:1,underline:1,"line-through":1,blink:1},"text-align":{left:1,right:1,center:1,justify:1},"text-transform":{capitalize:1,uppercase:1,lowercase:1,none:1},top:{px:1,em:1,"%":1},"vertical-align":{top:1,bottom:1},visibility:{hidden:1,visible:1},"white-space":{nowrap:1,normal:1,pre:1,"pre-line":1,"pre-wrap":1},width:{px:1,em:1,"%":1},"word-spacing":{normal:1},filter:{"alpha(opacity=$0100)":1},"text-shadow":{"$02px 2px 2px #777":1},"text-overflow":{"ellipsis-word":1,clip:1,ellipsis:1},"-moz-border-radius":1,"-moz-border-radius-topright":1,"-moz-border-radius-bottomright":1,"-moz-border-radius-topleft":1,"-moz-border-radius-bottomleft":1,"-webkit-border-radius":1,"-webkit-border-top-right-radius":1,"-webkit-border-top-left-radius":1,"-webkit-border-bottom-right-radius":1,"-webkit-border-bottom-left-radius":1,"-moz-box-shadow":1,"-webkit-box-shadow":1,transform:{"rotate($00deg)":1,"skew($00deg)":1},"-moz-transform":{"rotate($00deg)":1,"skew($00deg)":1},"-webkit-transform":{"rotate($00deg)":1,"skew($00deg)":1}},i=function(){};(function(){this.completionsDefined=!1,this.defineCompletions=function(){if(document){var e=document.createElement("c").style;for(var t in e){if(typeof e[t]!="string")continue;var n=t.replace(/[A-Z]/g,function(e){return"-"+e.toLowerCase()});r.hasOwnProperty(n)||(r[n]=1)}}this.completionsDefined=!0},this.getCompletions=function(e,t,n,r){this.completionsDefined||this.defineCompletions();var i=t.getTokenAt(n.row,n.column);if(!i)return[];if(e==="ruleset"){var s=t.getLine(n.row).substr(0,n.column);return/:[^;]+$/.test(s)?(/([\w\-]+):[^:]*$/.test(s),this.getPropertyValueCompletions(e,t,n,r)):this.getPropertyCompletions(e,t,n,r)}return[]},this.getPropertyCompletions=function(e,t,n,i){var s=Object.keys(r);return s.map(function(e){return{caption:e,snippet:e+": $0",meta:"property",score:Number.MAX_VALUE}})},this.getPropertyValueCompletions=function(e,t,n,i){var s=t.getLine(n.row).substr(0,n.column),o=(/([\w\-]+):[^:]*$/.exec(s)||{})[1];if(!o)return[];var u=[];return o in r&&typeof r[o]=="object"&&(u=Object.keys(r[o])),u.map(function(e){return{caption:e,snippet:e,meta:"property value",score:Number.MAX_VALUE}})}}).call(i.prototype),t.CssCompletions=i}),ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("../../token_iterator").TokenIterator,o=e("../../lib/lang"),u=["text","paren.rparen","punctuation.operator"],a=["text","paren.rparen","punctuation.operator","comment"],f,l={},c=function(e){var t=-1;e.multiSelect&&(t=e.selection.index,l.rangeCount!=e.multiSelect.rangeCount&&(l={rangeCount:e.multiSelect.rangeCount}));if(l[t])return f=l[t];f=l[t]={autoInsertedBrackets:0,autoInsertedRow:-1,autoInsertedLineEnd:"",maybeInsertedBrackets:0,maybeInsertedRow:-1,maybeInsertedLineStart:"",maybeInsertedLineEnd:""}},h=function(e,t,n,r){var i=e.end.row-e.start.row;return{text:n+t+r,selection:[0,e.start.column+1,i,e.end.column+(i?0:1)]}},p=function(){this.add("braces","insertion",function(e,t,n,r,i){var s=n.getCursorPosition(),u=r.doc.getLine(s.row);if(i=="{"){c(n);var a=n.getSelectionRange(),l=r.doc.getTextRange(a);if(l!==""&&l!=="{"&&n.getWrapBehavioursEnabled())return h(a,l,"{","}");if(p.isSaneInsertion(n,r))return/[\]\}\)]/.test(u[s.column])||n.inMultiSelectMode?(p.recordAutoInsert(n,r,"}"),{text:"{}",selection:[1,1]}):(p.recordMaybeInsert(n,r,"{"),{text:"{",selection:[1,1]})}else if(i=="}"){c(n);var d=u.substring(s.column,s.column+1);if(d=="}"){var v=r.$findOpeningBracket("}",{column:s.column+1,row:s.row});if(v!==null&&p.isAutoInsertedClosing(s,u,i))return p.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}else{if(i=="\n"||i=="\r\n"){c(n);var m="";p.isMaybeInsertedClosing(s,u)&&(m=o.stringRepeat("}",f.maybeInsertedBrackets),p.clearMaybeInsertedClosing());var d=u.substring(s.column,s.column+1);if(d==="}"){var g=r.findMatchingBracket({row:s.row,column:s.column+1},"}");if(!g)return null;var y=this.$getIndent(r.getLine(g.row))}else{if(!m){p.clearMaybeInsertedClosing();return}var y=this.$getIndent(u)}var b=y+r.getTabString();return{text:"\n"+b+"\n"+y+m,selection:[1,b.length,1,b.length]}}p.clearMaybeInsertedClosing()}}),this.add("braces","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="{"){c(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.end.column,i.end.column+1);if(u=="}")return i.end.column++,i;f.maybeInsertedBrackets--}}),this.add("parens","insertion",function(e,t,n,r,i){if(i=="("){c(n);var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return h(s,o,"(",")");if(p.isSaneInsertion(n,r))return p.recordAutoInsert(n,r,")"),{text:"()",selection:[1,1]}}else if(i==")"){c(n);var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f==")"){var l=r.$findOpeningBracket(")",{column:u.column+1,row:u.row});if(l!==null&&p.isAutoInsertedClosing(u,a,i))return p.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("parens","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="("){c(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==")")return i.end.column++,i}}),this.add("brackets","insertion",function(e,t,n,r,i){if(i=="["){c(n);var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return h(s,o,"[","]");if(p.isSaneInsertion(n,r))return p.recordAutoInsert(n,r,"]"),{text:"[]",selection:[1,1]}}else if(i=="]"){c(n);var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f=="]"){var l=r.$findOpeningBracket("]",{column:u.column+1,row:u.row});if(l!==null&&p.isAutoInsertedClosing(u,a,i))return p.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("brackets","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="["){c(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u=="]")return i.end.column++,i}}),this.add("string_dquotes","insertion",function(e,t,n,r,i){if(i=='"'||i=="'"){c(n);var s=i,o=n.getSelectionRange(),u=r.doc.getTextRange(o);if(u!==""&&u!=="'"&&u!='"'&&n.getWrapBehavioursEnabled())return h(o,u,s,s);if(!u){var a=n.getCursorPosition(),f=r.doc.getLine(a.row),l=f.substring(a.column-1,a.column),p=f.substring(a.column,a.column+1),d=r.getTokenAt(a.row,a.column),v=r.getTokenAt(a.row,a.column+1);if(l=="\\"&&d&&/escape/.test(d.type))return null;var m=d&&/string|escape/.test(d.type),g=!v||/string|escape/.test(v.type),y;if(p==s)y=m!==g;else{if(m&&!g)return null;if(m&&g)return null;var b=r.$mode.tokenRe;b.lastIndex=0;var w=b.test(l);b.lastIndex=0;var E=b.test(l);if(w||E)return null;if(p&&!/[\s;,.})\]\\]/.test(p))return null;y=!0}return{text:y?s+s:"",selection:[1,1]}}}}),this.add("string_dquotes","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&(s=='"'||s=="'")){c(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==s)return i.end.column++,i}})};p.isSaneInsertion=function(e,t){var n=e.getCursorPosition(),r=new s(t,n.row,n.column);if(!this.$matchTokenType(r.getCurrentToken()||"text",u)){var i=new s(t,n.row,n.column+1);if(!this.$matchTokenType(i.getCurrentToken()||"text",u))return!1}return r.stepForward(),r.getCurrentTokenRow()!==n.row||this.$matchTokenType(r.getCurrentToken()||"text",a)},p.$matchTokenType=function(e,t){return t.indexOf(e.type||e)>-1},p.recordAutoInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isAutoInsertedClosing(r,i,f.autoInsertedLineEnd[0])||(f.autoInsertedBrackets=0),f.autoInsertedRow=r.row,f.autoInsertedLineEnd=n+i.substr(r.column),f.autoInsertedBrackets++},p.recordMaybeInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isMaybeInsertedClosing(r,i)||(f.maybeInsertedBrackets=0),f.maybeInsertedRow=r.row,f.maybeInsertedLineStart=i.substr(0,r.column)+n,f.maybeInsertedLineEnd=i.substr(r.column),f.maybeInsertedBrackets++},p.isAutoInsertedClosing=function(e,t,n){return f.autoInsertedBrackets>0&&e.row===f.autoInsertedRow&&n===f.autoInsertedLineEnd[0]&&t.substr(e.column)===f.autoInsertedLineEnd},p.isMaybeInsertedClosing=function(e,t){return f.maybeInsertedBrackets>0&&e.row===f.maybeInsertedRow&&t.substr(e.column)===f.maybeInsertedLineEnd&&t.substr(0,e.column)==f.maybeInsertedLineStart},p.popAutoInsertedClosing=function(){f.autoInsertedLineEnd=f.autoInsertedLineEnd.substr(1),f.autoInsertedBrackets--},p.clearMaybeInsertedClosing=function(){f&&(f.maybeInsertedBrackets=0,f.maybeInsertedRow=-1)},r.inherits(p,i),t.CstyleBehaviour=p}),ace.define("ace/mode/behaviour/css",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/mode/behaviour/cstyle","ace/token_iterator"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("./cstyle").CstyleBehaviour,o=e("../../token_iterator").TokenIterator,u=function(){this.inherit(s),this.add("colon","insertion",function(e,t,n,r,i){if(i===":"){var s=n.getCursorPosition(),u=new o(r,s.row,s.column),a=u.getCurrentToken();a&&a.value.match(/\s+/)&&(a=u.stepBackward());if(a&&a.type==="support.type"){var f=r.doc.getLine(s.row),l=f.substring(s.column,s.column+1);if(l===":")return{text:"",selection:[1,1]};if(!f.substring(s.column).match(/^\s*;/))return{text:":;",selection:[1,1]}}}}),this.add("colon","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s===":"){var u=n.getCursorPosition(),a=new o(r,u.row,u.column),f=a.getCurrentToken();f&&f.value.match(/\s+/)&&(f=a.stepBackward());if(f&&f.type==="support.type"){var l=r.doc.getLine(i.start.row),c=l.substring(i.end.column,i.end.column+1);if(c===";")return i.end.column++,i}}}),this.add("semicolon","insertion",function(e,t,n,r,i){if(i===";"){var s=n.getCursorPosition(),o=r.doc.getLine(s.row),u=o.substring(s.column,s.column+1);if(u===";")return{text:"",selection:[1,1]}}})};r.inherits(u,s),t.CssBehaviour=u}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),ace.define("ace/mode/css",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/css_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/css_completions","ace/mode/behaviour/css","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./css_highlight_rules").CssHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../worker/worker_client").WorkerClient,a=e("./css_completions").CssCompletions,f=e("./behaviour/css").CssBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new f,this.$completer=new a,this.foldingRules=new l};r.inherits(c,i),function(){this.foldingRules="cStyle",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e).tokens;if(i.length&&i[i.length-1].type=="comment")return r;var s=t.match(/^.*\{\s*$/);return s&&(r+=n),r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.getCompletions=function(e,t,n,r){return this.$completer.getCompletions(e,t,n,r)},this.createWorker=function(e){var t=new u(["ace"],"ace/mode/css_worker","Worker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/css"}.call(c.prototype),t.Mode=c}) \ No newline at end of file diff --git a/public/ace/mode-plain_text.js b/public/ace/mode-plain_text.js new file mode 100644 index 00000000..21cf21da --- /dev/null +++ b/public/ace/mode-plain_text.js @@ -0,0 +1 @@ +ace.define("ace/mode/plain_text",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/text_highlight_rules","ace/mode/behaviour"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./text_highlight_rules").TextHighlightRules,o=e("./behaviour").Behaviour,u=function(){this.HighlightRules=s,this.$behaviour=new o};r.inherits(u,i),function(){this.type="text",this.getNextLineIndent=function(e,t,n){return""},this.$id="ace/mode/plain_text"}.call(u.prototype),t.Mode=u}) diff --git a/public/ace/worker-css.js b/public/ace/worker-css.js new file mode 100644 index 00000000..408c6bd0 --- /dev/null +++ b/public/ace/worker-css.js @@ -0,0 +1 @@ +"no use strict";(function(e){function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")return i+r;if(i)return i.location.replace(/\/*$/,"/")+(r||i.main||i.name);if(i===!1)return"";var s=n.lastIndexOf("/");if(s===-1)break;r=n.substr(s)+r,n=n.slice(0,s)}return e}if(typeof e.window!="undefined"&&e.document)return;if(e.require&&e.define)return;e.console||(e.console=function(){var e=Array.prototype.slice.call(arguments,0);postMessage({type:"log",data:e})},e.console.error=e.console.warn=e.console.log=e.console.trace=e.console),e.window=e,e.ace=e,e.onerror=function(e,t,n,r,i){postMessage({type:"error",data:{message:e,data:i.data,file:t,line:n,col:r,stack:i.stack}})},e.normalizeModule=function(t,n){if(n.indexOf("!")!==-1){var r=n.split("!");return e.normalizeModule(t,r[0])+"!"+e.normalizeModule(t,r[1])}if(n.charAt(0)=="."){var i=t.split("/").slice(0,-1).join("/");n=(i?i+"/":"")+n;while(n.indexOf(".")!==-1&&s!=n){var s=n;n=n.replace(/^\.\//,"").replace(/\/\.\//,"/").replace(/[^\/]+\/\.\.\//,"")}}return n},e.require=function(r,i){i||(i=r,r=null);if(!i.charAt)throw new Error("worker.js require() accepts only (parentId, id) as arguments");i=e.normalizeModule(r,i);var s=e.require.modules[i];if(s)return s.initialized||(s.initialized=!0,s.exports=s.factory().exports),s.exports;if(!e.require.tlns)return console.log("unable to load "+i);var o=t(i,e.require.tlns);return o.slice(-3)!=".js"&&(o+=".js"),e.require.id=i,e.require.modules[i]={},importScripts(o),e.require(r,i)},e.require.modules={},e.require.tlns={},e.define=function(t,n,r){arguments.length==2?(r=n,typeof t!="string"&&(n=t,t=e.require.id)):arguments.length==1&&(r=t,n=[],t=e.require.id);if(typeof r!="function"){e.require.modules[t]={exports:r,initialized:!0};return}n.length||(n=["require","exports","module"]);var i=function(n){return e.require(t,n)};e.require.modules[t]={exports:{},factory:function(){var e=this,t=r.apply(this,n.map(function(t){switch(t){case"require":return i;case"exports":return e.exports;case"module":return e;default:return i(t)}}));return t&&(e.exports=t),e}}},e.define.amd={},require.tlns={},e.initBaseUrls=function(t){for(var n in t)require.tlns[n]=t[n]},e.initSender=function(){var n=e.require("ace/lib/event_emitter").EventEmitter,r=e.require("ace/lib/oop"),i=function(){};return function(){r.implement(this,n),this.callback=function(e,t){postMessage({type:"call",id:t,data:e})},this.emit=function(e,t){postMessage({type:"event",name:e,data:t})}}.call(i.prototype),new i};var n=e.main=null,r=e.sender=null;e.onmessage=function(t){var i=t.data;if(i.event&&r)r._signal(i.event,i.data);else if(i.command)if(n[i.command])n[i.command].apply(n,i.args);else{if(!e[i.command])throw new Error("Unknown command:"+i.command);e[i.command].apply(e,i.args)}else if(i.init){e.initBaseUrls(i.tlns),require("ace/lib/es5-shim"),r=e.sender=e.initSender();var s=require(i.module)[i.classname];n=e.main=new s(r)}}})(this),ace.define("ace/lib/oop",["require","exports","module"],function(e,t,n){"use strict";t.inherits=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},t.mixin=function(e,t){for(var n in t)e[n]=t[n];return e},t.implement=function(e,n){t.mixin(e,n)}}),ace.define("ace/lib/lang",["require","exports","module"],function(e,t,n){"use strict";t.last=function(e){return e[e.length-1]},t.stringReverse=function(e){return e.split("").reverse().join("")},t.stringRepeat=function(e,t){var n="";while(t>0){t&1&&(n+=e);if(t>>=1)e+=e}return n};var r=/^\s\s*/,i=/\s\s*$/;t.stringTrimLeft=function(e){return e.replace(r,"")},t.stringTrimRight=function(e){return e.replace(i,"")},t.copyObject=function(e){var t={};for(var n in e)t[n]=e[n];return t},t.copyArray=function(e){var t=[];for(var n=0,r=e.length;n ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(e,t){return this.compare(e,t)==0},this.compareRange=function(e){var t,n=e.end,r=e.start;return t=this.compare(n.row,n.column),t==1?(t=this.compare(r.row,r.column),t==1?2:t==0?1:0):t==-1?-2:(t=this.compare(r.row,r.column),t==-1?-1:t==1?42:0)},this.comparePoint=function(e){return this.compare(e.row,e.column)},this.containsRange=function(e){return this.comparePoint(e.start)==0&&this.comparePoint(e.end)==0},this.intersects=function(e){var t=this.compareRange(e);return t==-1||t==0||t==1},this.isEnd=function(e,t){return this.end.row==e&&this.end.column==t},this.isStart=function(e,t){return this.start.row==e&&this.start.column==t},this.setStart=function(e,t){typeof e=="object"?(this.start.column=e.column,this.start.row=e.row):(this.start.row=e,this.start.column=t)},this.setEnd=function(e,t){typeof e=="object"?(this.end.column=e.column,this.end.row=e.row):(this.end.row=e,this.end.column=t)},this.inside=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)||this.isStart(e,t)?!1:!0:!1},this.insideStart=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)?!1:!0:!1},this.insideEnd=function(e,t){return this.compare(e,t)==0?this.isStart(e,t)?!1:!0:!1},this.compare=function(e,t){return!this.isMultiLine()&&e===this.start.row?tthis.end.column?1:0:ethis.end.row?1:this.start.row===e?t>=this.start.column?0:-1:this.end.row===e?t<=this.end.column?0:1:0},this.compareStart=function(e,t){return this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.compareEnd=function(e,t){return this.end.row==e&&this.end.column==t?1:this.compare(e,t)},this.compareInside=function(e,t){return this.end.row==e&&this.end.column==t?1:this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.clipRows=function(e,t){if(this.end.row>t)var n={row:t+1,column:0};else if(this.end.rowt)var r={row:t+1,column:0};else if(this.start.row=0&&t.row=0&&t.column<=e[t.row].length}function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.action must be 'insert' or 'remove'"),t.lines instanceof Array||r(t,"delta.lines must be an Array"),(!t.start||!t.end)&&r(t,"delta.start/end must be an present");var n=t.start;i(e,t.start)||r(t,"delta.start must be contained in document");var s=t.end;t.action=="remove"&&!i(e,s)&&r(t,"delta.end must contained in document for 'remove' actions");var o=s.row-n.row,u=s.column-(o==0?n.column:0);(o!=t.lines.length-1||t.lines[o].length!=u)&&r(t,"delta.range must match delta lines")}t.applyDelta=function(e,t,n){var r=t.start.row,i=t.start.column,s=e[r]||"";switch(t.action){case"insert":var o=t.lines;if(o.length===1)e[r]=s.substring(0,i)+t.lines[0]+s.substring(i);else{var u=[r,1].concat(t.lines);e.splice.apply(e,u),e[r]=s.substring(0,i)+e[r],e[r+t.lines.length-1]+=s.substring(i)}break;case"remove":var a=t.end.column,f=t.end.row;r===f?e[r]=s.substring(0,i)+s.substring(a):e.splice(r,f-r+1,s.substring(0,i)+e[f].substring(a))}}}),ace.define("ace/lib/event_emitter",["require","exports","module"],function(e,t,n){"use strict";var r={},i=function(){this.propagationStopped=!0},s=function(){this.defaultPrevented=!0};r._emit=r._dispatchEvent=function(e,t){this._eventRegistry||(this._eventRegistry={}),this._defaultHandlers||(this._defaultHandlers={});var n=this._eventRegistry[e]||[],r=this._defaultHandlers[e];if(!n.length&&!r)return;if(typeof t!="object"||!t)t={};t.type||(t.type=e),t.stopPropagation||(t.stopPropagation=i),t.preventDefault||(t.preventDefault=s),n=n.slice();for(var o=0;othis.row)return;var n=t(e,{row:this.row,column:this.column},this.$insertRight);this.setPosition(n.row,n.column,!0)},this.setPosition=function(e,t,n){var r;n?r={row:e,column:t}:r=this.$clipPositionToDocument(e,t);if(this.row==r.row&&this.column==r.column)return;var i={row:this.row,column:this.column};this.row=r.row,this.column=r.column,this._signal("change",{old:i,value:r})},this.detach=function(){this.document.removeEventListener("change",this.$onChange)},this.attach=function(e){this.document=e||this.document,this.document.on("change",this.$onChange)},this.$clipPositionToDocument=function(e,t){var n={};return e>=this.document.getLength()?(n.row=Math.max(0,this.document.getLength()-1),n.column=this.document.getLine(n.row).length):e<0?(n.row=0,n.column=0):(n.row=e,n.column=Math.min(this.document.getLine(n.row).length,Math.max(0,t))),t<0&&(n.column=0),n}}).call(s.prototype)}),ace.define("ace/document",["require","exports","module","ace/lib/oop","ace/apply_delta","ace/lib/event_emitter","ace/range","ace/anchor"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./apply_delta").applyDelta,s=e("./lib/event_emitter").EventEmitter,o=e("./range").Range,u=e("./anchor").Anchor,a=function(e){this.$lines=[""],e.length===0?this.$lines=[""]:Array.isArray(e)?this.insertMergedLines({row:0,column:0},e):this.insert({row:0,column:0},e)};(function(){r.implement(this,s),this.setValue=function(e){var t=this.getLength()-1;this.remove(new o(0,0,t,this.getLine(t).length)),this.insert({row:0,column:0},e)},this.getValue=function(){return this.getAllLines().join(this.getNewLineCharacter())},this.createAnchor=function(e,t){return new u(this,e,t)},"aaa".split(/a/).length===0?this.$split=function(e){return e.replace(/\r\n|\r/g,"\n").split("\n")}:this.$split=function(e){return e.split(/\r\n|\r|\n/)},this.$detectNewLine=function(e){var t=e.match(/^.*?(\r\n|\r|\n)/m);this.$autoNewLine=t?t[1]:"\n",this._signal("changeNewLineMode")},this.getNewLineCharacter=function(){switch(this.$newLineMode){case"windows":return"\r\n";case"unix":return"\n";default:return this.$autoNewLine||"\n"}},this.$autoNewLine="",this.$newLineMode="auto",this.setNewLineMode=function(e){if(this.$newLineMode===e)return;this.$newLineMode=e,this._signal("changeNewLineMode")},this.getNewLineMode=function(){return this.$newLineMode},this.isNewLine=function(e){return e=="\r\n"||e=="\r"||e=="\n"},this.getLine=function(e){return this.$lines[e]||""},this.getLines=function(e,t){return this.$lines.slice(e,t+1)},this.getAllLines=function(){return this.getLines(0,this.getLength())},this.getLength=function(){return this.$lines.length},this.getTextRange=function(e){return this.getLinesForRange(e).join(this.getNewLineCharacter())},this.getLinesForRange=function(e){var t;if(e.start.row===e.end.row)t=[this.getLine(e.start.row).substring(e.start.column,e.end.column)];else{t=this.getLines(e.start.row,e.end.row),t[0]=(t[0]||"").substring(e.start.column);var n=t.length-1;e.end.row-e.start.row==n&&(t[n]=t[n].substring(0,e.end.column))}return t},this.insertLines=function(e,t){return console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."),this.insertFullLines(e,t)},this.removeLines=function(e,t){return console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."),this.removeFullLines(e,t)},this.insertNewLine=function(e){return console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."),this.insertMergedLines(e,["",""])},this.insert=function(e,t){return this.getLength()<=1&&this.$detectNewLine(t),this.insertMergedLines(e,this.$split(t))},this.insertInLine=function(e,t){var n=this.clippedPos(e.row,e.column),r=this.pos(e.row,e.column+t.length);return this.applyDelta({start:n,end:r,action:"insert",lines:[t]},!0),this.clonePos(r)},this.clippedPos=function(e,t){var n=this.getLength();e===undefined?e=n:e<0?e=0:e>=n&&(e=n-1,t=undefined);var r=this.getLine(e);return t==undefined&&(t=r.length),t=Math.min(Math.max(t,0),r.length),{row:e,column:t}},this.clonePos=function(e){return{row:e.row,column:e.column}},this.pos=function(e,t){return{row:e,column:t}},this.$clipPosition=function(e){var t=this.getLength();return e.row>=t?(e.row=Math.max(0,t-1),e.column=this.getLine(t-1).length):(e.row=Math.max(0,e.row),e.column=Math.min(Math.max(e.column,0),this.getLine(e.row).length)),e},this.insertFullLines=function(e,t){e=Math.min(Math.max(e,0),this.getLength());var n=0;e0,r=t=0&&this.applyDelta({start:this.pos(e,this.getLine(e).length),end:this.pos(e+1,0),action:"remove",lines:["",""]})},this.replace=function(e,t){e instanceof o||(e=o.fromPoints(e.start,e.end));if(t.length===0&&e.isEmpty())return e.start;if(t==this.getTextRange(e))return e.end;this.remove(e);var n;return t?n=this.insert(e.start,t):n=e.start,n},this.applyDeltas=function(e){for(var t=0;t=0;t--)this.revertDelta(e[t])},this.applyDelta=function(e,t){var n=e.action=="insert";if(n?e.lines.length<=1&&!e.lines[0]:!o.comparePoints(e.start,e.end))return;n&&e.lines.length>2e4&&this.$splitAndapplyLargeDelta(e,2e4),i(this.$lines,e,t),this._signal("change",e)},this.$splitAndapplyLargeDelta=function(e,t){var n=e.lines,r=n.length,i=e.start.row,s=e.start.column,o=0,u=0;do{o=u,u+=t-1;var a=n.slice(o,u);if(u>r){e.lines=a,e.start.row=i+o,e.start.column=s;break}a.push(""),this.applyDelta({start:this.pos(i+o,s),end:this.pos(i+u,s=0),action:e.action,lines:a},!0)}while(!0)},this.revertDelta=function(e){this.applyDelta({start:this.clonePos(e.start),end:this.clonePos(e.end),action:e.action=="insert"?"remove":"insert",lines:e.lines.slice()})},this.indexToPosition=function(e,t){var n=this.$lines||this.getAllLines(),r=this.getNewLineCharacter().length;for(var i=t||0,s=n.length;i=0&&this._ltIndex-1&&!t[u.type].hide&&(u.channel=t[u.type].channel,this._token=u,this._lt.push(u),this._ltIndexCache.push(this._lt.length-this._ltIndex+i),this._lt.length>5&&this._lt.shift(),this._ltIndexCache.length>5&&this._ltIndexCache.shift(),this._ltIndex=this._lt.length),a=t[u.type],a&&(a.hide||a.channel!==undefined&&e!==a.channel)?this.get(e):u.type},LA:function(e){var t=e,n;if(e>0){if(e>5)throw new Error("Too much lookahead.");while(t)n=this.get(),t--;while(tthis._tokenData.length?"UNKNOWN_TOKEN":this._tokenData[e].name},tokenType:function(e){return this._tokenData[e]||-1},unget:function(){if(!this._ltIndexCache.length)throw new Error("Too much lookahead.");this._ltIndex-=this._ltIndexCache.pop(),this._token=this._lt[this._ltIndex-1]}},parserlib.util={StringReader:t,SyntaxError:n,SyntaxUnit:r,EventTarget:e,TokenStreamBase:i}})(),function(){function Combinator(e,t,n){SyntaxUnit.call(this,e,t,n,Parser.COMBINATOR_TYPE),this.type="unknown",/^\s+$/.test(e)?this.type="descendant":e==">"?this.type="child":e=="+"?this.type="adjacent-sibling":e=="~"&&(this.type="sibling")}function MediaFeature(e,t){SyntaxUnit.call(this,"("+e+(t!==null?":"+t:"")+")",e.startLine,e.startCol,Parser.MEDIA_FEATURE_TYPE),this.name=e,this.value=t}function MediaQuery(e,t,n,r,i){SyntaxUnit.call(this,(e?e+" ":"")+(t?t:"")+(t&&n.length>0?" and ":"")+n.join(" and "),r,i,Parser.MEDIA_QUERY_TYPE),this.modifier=e,this.mediaType=t,this.features=n}function Parser(e){EventTarget.call(this),this.options=e||{},this._tokenStream=null}function PropertyName(e,t,n,r){SyntaxUnit.call(this,e,n,r,Parser.PROPERTY_NAME_TYPE),this.hack=t}function PropertyValue(e,t,n){SyntaxUnit.call(this,e.join(" "),t,n,Parser.PROPERTY_VALUE_TYPE),this.parts=e}function PropertyValueIterator(e){this._i=0,this._parts=e.parts,this._marks=[],this.value=e}function PropertyValuePart(text,line,col){SyntaxUnit.call(this,text,line,col,Parser.PROPERTY_VALUE_PART_TYPE),this.type="unknown";var temp;if(/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)){this.type="dimension",this.value=+RegExp.$1,this.units=RegExp.$2;switch(this.units.toLowerCase()){case"em":case"rem":case"ex":case"px":case"cm":case"mm":case"in":case"pt":case"pc":case"ch":case"vh":case"vw":case"vmax":case"vmin":this.type="length";break;case"deg":case"rad":case"grad":this.type="angle";break;case"ms":case"s":this.type="time";break;case"hz":case"khz":this.type="frequency";break;case"dpi":case"dpcm":this.type="resolution"}}else/^([+\-]?[\d\.]+)%$/i.test(text)?(this.type="percentage",this.value=+RegExp.$1):/^([+\-]?\d+)$/i.test(text)?(this.type="integer",this.value=+RegExp.$1):/^([+\-]?[\d\.]+)$/i.test(text)?(this.type="number",this.value=+RegExp.$1):/^#([a-f0-9]{3,6})/i.test(text)?(this.type="color",temp=RegExp.$1,temp.length==3?(this.red=parseInt(temp.charAt(0)+temp.charAt(0),16),this.green=parseInt(temp.charAt(1)+temp.charAt(1),16),this.blue=parseInt(temp.charAt(2)+temp.charAt(2),16)):(this.red=parseInt(temp.substring(0,2),16),this.green=parseInt(temp.substring(2,4),16),this.blue=parseInt(temp.substring(4,6),16))):/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)?(this.type="color",this.red=+RegExp.$1,this.green=+RegExp.$2,this.blue=+RegExp.$3):/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)?(this.type="color",this.red=+RegExp.$1*255/100,this.green=+RegExp.$2*255/100,this.blue=+RegExp.$3*255/100):/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)?(this.type="color",this.red=+RegExp.$1,this.green=+RegExp.$2,this.blue=+RegExp.$3,this.alpha=+RegExp.$4):/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)?(this.type="color",this.red=+RegExp.$1*255/100,this.green=+RegExp.$2*255/100,this.blue=+RegExp.$3*255/100,this.alpha=+RegExp.$4):/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)?(this.type="color",this.hue=+RegExp.$1,this.saturation=+RegExp.$2/100,this.lightness=+RegExp.$3/100):/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)?(this.type="color",this.hue=+RegExp.$1,this.saturation=+RegExp.$2/100,this.lightness=+RegExp.$3/100,this.alpha=+RegExp.$4):/^url\(["']?([^\)"']+)["']?\)/i.test(text)?(this.type="uri",this.uri=RegExp.$1):/^([^\(]+)\(/i.test(text)?(this.type="function",this.name=RegExp.$1,this.value=text):/^["'][^"']*["']/.test(text)?(this.type="string",this.value=eval(text)):Colors[text.toLowerCase()]?(this.type="color",temp=Colors[text.toLowerCase()].substring(1),this.red=parseInt(temp.substring(0,2),16),this.green=parseInt(temp.substring(2,4),16),this.blue=parseInt(temp.substring(4,6),16)):/^[\,\/]$/.test(text)?(this.type="operator",this.value=text):/^[a-z\-_\u0080-\uFFFF][a-z0-9\-_\u0080-\uFFFF]*$/i.test(text)&&(this.type="identifier",this.value=text)}function Selector(e,t,n){SyntaxUnit.call(this,e.join(" "),t,n,Parser.SELECTOR_TYPE),this.parts=e,this.specificity=Specificity.calculate(this)}function SelectorPart(e,t,n,r,i){SyntaxUnit.call(this,n,r,i,Parser.SELECTOR_PART_TYPE),this.elementName=e,this.modifiers=t}function SelectorSubPart(e,t,n,r){SyntaxUnit.call(this,e,n,r,Parser.SELECTOR_SUB_PART_TYPE),this.type=t,this.args=[]}function Specificity(e,t,n,r){this.a=e,this.b=t,this.c=n,this.d=r}function isHexDigit(e){return e!==null&&h.test(e)}function isDigit(e){return e!==null&&/\d/.test(e)}function isWhitespace(e){return e!==null&&/\s/.test(e)}function isNewLine(e){return e!==null&&nl.test(e)}function isNameStart(e){return e!==null&&/[a-z_\u0080-\uFFFF\\]/i.test(e)}function isNameChar(e){return e!==null&&(isNameStart(e)||/[0-9\-\\]/.test(e))}function isIdentStart(e){return e!==null&&(isNameStart(e)||/\-\\/.test(e))}function mix(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function TokenStream(e){TokenStreamBase.call(this,e,Tokens)}function ValidationError(e,t,n){this.col=n,this.line=t,this.message=e}var EventTarget=parserlib.util.EventTarget,TokenStreamBase=parserlib.util.TokenStreamBase,StringReader=parserlib.util.StringReader,SyntaxError=parserlib.util.SyntaxError,SyntaxUnit=parserlib.util.SyntaxUnit,Colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32",activeBorder:"Active window border.",activecaption:"Active window caption.",appworkspace:"Background color of multiple document interface.",background:"Desktop background.",buttonface:"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",buttonhighlight:"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",buttonshadow:"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",buttontext:"Text on push buttons.",captiontext:"Text in caption, size box, and scrollbar arrow box.",graytext:"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",greytext:"Greyed (disabled) text. This color is set to #000 if the current display driver does not support a solid grey color.",highlight:"Item(s) selected in a control.",highlighttext:"Text of item(s) selected in a control.",inactiveborder:"Inactive window border.",inactivecaption:"Inactive window caption.",inactivecaptiontext:"Color of text in an inactive caption.",infobackground:"Background color for tooltip controls.",infotext:"Text color for tooltip controls.",menu:"Menu background.",menutext:"Text in menus.",scrollbar:"Scroll bar gray area.",threeddarkshadow:"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",threedface:"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",threedhighlight:"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",threedlightshadow:"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",threedshadow:"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",window:"Window background.",windowframe:"Window frame.",windowtext:"Text in windows."};Combinator.prototype=new SyntaxUnit,Combinator.prototype.constructor=Combinator,MediaFeature.prototype=new SyntaxUnit,MediaFeature.prototype.constructor=MediaFeature,MediaQuery.prototype=new SyntaxUnit,MediaQuery.prototype.constructor=MediaQuery,Parser.DEFAULT_TYPE=0,Parser.COMBINATOR_TYPE=1,Parser.MEDIA_FEATURE_TYPE=2,Parser.MEDIA_QUERY_TYPE=3,Parser.PROPERTY_NAME_TYPE=4,Parser.PROPERTY_VALUE_TYPE=5,Parser.PROPERTY_VALUE_PART_TYPE=6,Parser.SELECTOR_TYPE=7,Parser.SELECTOR_PART_TYPE=8,Parser.SELECTOR_SUB_PART_TYPE=9,Parser.prototype=function(){var e=new EventTarget,t,n={constructor:Parser,DEFAULT_TYPE:0,COMBINATOR_TYPE:1,MEDIA_FEATURE_TYPE:2,MEDIA_QUERY_TYPE:3,PROPERTY_NAME_TYPE:4,PROPERTY_VALUE_TYPE:5,PROPERTY_VALUE_PART_TYPE:6,SELECTOR_TYPE:7,SELECTOR_PART_TYPE:8,SELECTOR_SUB_PART_TYPE:9,_stylesheet:function(){var e=this._tokenStream,t=null,n,r,i;this.fire("startstylesheet"),this._charset(),this._skipCruft();while(e.peek()==Tokens.IMPORT_SYM)this._import(),this._skipCruft();while(e.peek()==Tokens.NAMESPACE_SYM)this._namespace(),this._skipCruft();i=e.peek();while(i>Tokens.EOF){try{switch(i){case Tokens.MEDIA_SYM:this._media(),this._skipCruft();break;case Tokens.PAGE_SYM:this._page(),this._skipCruft();break;case Tokens.FONT_FACE_SYM:this._font_face(),this._skipCruft();break;case Tokens.KEYFRAMES_SYM:this._keyframes(),this._skipCruft();break;case Tokens.VIEWPORT_SYM:this._viewport(),this._skipCruft();break;case Tokens.UNKNOWN_SYM:e.get();if(!!this.options.strict)throw new SyntaxError("Unknown @ rule.",e.LT(0).startLine,e.LT(0).startCol);this.fire({type:"error",error:null,message:"Unknown @ rule: "+e.LT(0).value+".",line:e.LT(0).startLine,col:e.LT(0).startCol}),n=0;while(e.advance([Tokens.LBRACE,Tokens.RBRACE])==Tokens.LBRACE)n++;while(n)e.advance([Tokens.RBRACE]),n--;break;case Tokens.S:this._readWhitespace();break;default:if(!this._ruleset())switch(i){case Tokens.CHARSET_SYM:throw r=e.LT(1),this._charset(!1),new SyntaxError("@charset not allowed here.",r.startLine,r.startCol);case Tokens.IMPORT_SYM:throw r=e.LT(1),this._import(!1),new SyntaxError("@import not allowed here.",r.startLine,r.startCol);case Tokens.NAMESPACE_SYM:throw r=e.LT(1),this._namespace(!1),new SyntaxError("@namespace not allowed here.",r.startLine,r.startCol);default:e.get(),this._unexpectedToken(e.token())}}}catch(s){if(!(s instanceof SyntaxError&&!this.options.strict))throw s;this.fire({type:"error",error:s,message:s.message,line:s.line,col:s.col})}i=e.peek()}i!=Tokens.EOF&&this._unexpectedToken(e.token()),this.fire("endstylesheet")},_charset:function(e){var t=this._tokenStream,n,r,i,s;t.match(Tokens.CHARSET_SYM)&&(i=t.token().startLine,s=t.token().startCol,this._readWhitespace(),t.mustMatch(Tokens.STRING),r=t.token(),n=r.value,this._readWhitespace(),t.mustMatch(Tokens.SEMICOLON),e!==!1&&this.fire({type:"charset",charset:n,line:i,col:s}))},_import:function(e){var t=this._tokenStream,n,r,i,s=[];t.mustMatch(Tokens.IMPORT_SYM),i=t.token(),this._readWhitespace(),t.mustMatch([Tokens.STRING,Tokens.URI]),r=t.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/,"$1"),this._readWhitespace(),s=this._media_query_list(),t.mustMatch(Tokens.SEMICOLON),this._readWhitespace(),e!==!1&&this.fire({type:"import",uri:r,media:s,line:i.startLine,col:i.startCol})},_namespace:function(e){var t=this._tokenStream,n,r,i,s;t.mustMatch(Tokens.NAMESPACE_SYM),n=t.token().startLine,r=t.token().startCol,this._readWhitespace(),t.match(Tokens.IDENT)&&(i=t.token().value,this._readWhitespace()),t.mustMatch([Tokens.STRING,Tokens.URI]),s=t.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/,"$1"),this._readWhitespace(),t.mustMatch(Tokens.SEMICOLON),this._readWhitespace(),e!==!1&&this.fire({type:"namespace",prefix:i,uri:s,line:n,col:r})},_media:function(){var e=this._tokenStream,t,n,r;e.mustMatch(Tokens.MEDIA_SYM),t=e.token().startLine,n=e.token().startCol,this._readWhitespace(),r=this._media_query_list(),e.mustMatch(Tokens.LBRACE),this._readWhitespace(),this.fire({type:"startmedia",media:r,line:t,col:n});for(;;)if(e.peek()==Tokens.PAGE_SYM)this._page();else if(e.peek()==Tokens.FONT_FACE_SYM)this._font_face();else if(e.peek()==Tokens.VIEWPORT_SYM)this._viewport();else if(!this._ruleset())break;e.mustMatch(Tokens.RBRACE),this._readWhitespace(),this.fire({type:"endmedia",media:r,line:t,col:n})},_media_query_list:function(){var e=this._tokenStream,t=[];this._readWhitespace(),(e.peek()==Tokens.IDENT||e.peek()==Tokens.LPAREN)&&t.push(this._media_query());while(e.match(Tokens.COMMA))this._readWhitespace(),t.push(this._media_query());return t},_media_query:function(){var e=this._tokenStream,t=null,n=null,r=null,i=[];e.match(Tokens.IDENT)&&(n=e.token().value.toLowerCase(),n!="only"&&n!="not"?(e.unget(),n=null):r=e.token()),this._readWhitespace(),e.peek()==Tokens.IDENT?(t=this._media_type(),r===null&&(r=e.token())):e.peek()==Tokens.LPAREN&&(r===null&&(r=e.LT(1)),i.push(this._media_expression()));if(t===null&&i.length===0)return null;this._readWhitespace();while(e.match(Tokens.IDENT))e.token().value.toLowerCase()!="and"&&this._unexpectedToken(e.token()),this._readWhitespace(),i.push(this._media_expression());return new MediaQuery(n,t,i,r.startLine,r.startCol)},_media_type:function(){return this._media_feature()},_media_expression:function(){var e=this._tokenStream,t=null,n,r=null;return e.mustMatch(Tokens.LPAREN),t=this._media_feature(),this._readWhitespace(),e.match(Tokens.COLON)&&(this._readWhitespace(),n=e.LT(1),r=this._expression()),e.mustMatch(Tokens.RPAREN),this._readWhitespace(),new MediaFeature(t,r?new SyntaxUnit(r,n.startLine,n.startCol):null)},_media_feature:function(){var e=this._tokenStream;return e.mustMatch(Tokens.IDENT),SyntaxUnit.fromToken(e.token())},_page:function(){var e=this._tokenStream,t,n,r=null,i=null;e.mustMatch(Tokens.PAGE_SYM),t=e.token().startLine,n=e.token().startCol,this._readWhitespace(),e.match(Tokens.IDENT)&&(r=e.token().value,r.toLowerCase()==="auto"&&this._unexpectedToken(e.token())),e.peek()==Tokens.COLON&&(i=this._pseudo_page()),this._readWhitespace(),this.fire({type:"startpage",id:r,pseudo:i,line:t,col:n}),this._readDeclarations(!0,!0),this.fire({type:"endpage",id:r,pseudo:i,line:t,col:n})},_margin:function(){var e=this._tokenStream,t,n,r=this._margin_sym();return r?(t=e.token().startLine,n=e.token().startCol,this.fire({type:"startpagemargin",margin:r,line:t,col:n}),this._readDeclarations(!0),this.fire({type:"endpagemargin",margin:r,line:t,col:n}),!0):!1},_margin_sym:function(){var e=this._tokenStream;return e.match([Tokens.TOPLEFTCORNER_SYM,Tokens.TOPLEFT_SYM,Tokens.TOPCENTER_SYM,Tokens.TOPRIGHT_SYM,Tokens.TOPRIGHTCORNER_SYM,Tokens.BOTTOMLEFTCORNER_SYM,Tokens.BOTTOMLEFT_SYM,Tokens.BOTTOMCENTER_SYM,Tokens.BOTTOMRIGHT_SYM,Tokens.BOTTOMRIGHTCORNER_SYM,Tokens.LEFTTOP_SYM,Tokens.LEFTMIDDLE_SYM,Tokens.LEFTBOTTOM_SYM,Tokens.RIGHTTOP_SYM,Tokens.RIGHTMIDDLE_SYM,Tokens.RIGHTBOTTOM_SYM])?SyntaxUnit.fromToken(e.token()):null},_pseudo_page:function(){var e=this._tokenStream;return e.mustMatch(Tokens.COLON),e.mustMatch(Tokens.IDENT),e.token().value},_font_face:function(){var e=this._tokenStream,t,n;e.mustMatch(Tokens.FONT_FACE_SYM),t=e.token().startLine,n=e.token().startCol,this._readWhitespace(),this.fire({type:"startfontface",line:t,col:n}),this._readDeclarations(!0),this.fire({type:"endfontface",line:t,col:n})},_viewport:function(){var e=this._tokenStream,t,n;e.mustMatch(Tokens.VIEWPORT_SYM),t=e.token().startLine,n=e.token().startCol,this._readWhitespace(),this.fire({type:"startviewport",line:t,col:n}),this._readDeclarations(!0),this.fire({type:"endviewport",line:t,col:n})},_operator:function(e){var t=this._tokenStream,n=null;if(t.match([Tokens.SLASH,Tokens.COMMA])||e&&t.match([Tokens.PLUS,Tokens.STAR,Tokens.MINUS]))n=t.token(),this._readWhitespace();return n?PropertyValuePart.fromToken(n):null},_combinator:function(){var e=this._tokenStream,t=null,n;return e.match([Tokens.PLUS,Tokens.GREATER,Tokens.TILDE])&&(n=e.token(),t=new Combinator(n.value,n.startLine,n.startCol),this._readWhitespace()),t},_unary_operator:function(){var e=this._tokenStream;return e.match([Tokens.MINUS,Tokens.PLUS])?e.token().value:null},_property:function(){var e=this._tokenStream,t=null,n=null,r,i,s,o;return e.peek()==Tokens.STAR&&this.options.starHack&&(e.get(),i=e.token(),n=i.value,s=i.startLine,o=i.startCol),e.match(Tokens.IDENT)&&(i=e.token(),r=i.value,r.charAt(0)=="_"&&this.options.underscoreHack&&(n="_",r=r.substring(1)),t=new PropertyName(r,n,s||i.startLine,o||i.startCol),this._readWhitespace()),t},_ruleset:function(){var e=this._tokenStream,t,n;try{n=this._selectors_group()}catch(r){if(r instanceof SyntaxError&&!this.options.strict){this.fire({type:"error",error:r,message:r.message,line:r.line,col:r.col}),t=e.advance([Tokens.RBRACE]);if(t!=Tokens.RBRACE)throw r;return!0}throw r}return n&&(this.fire({type:"startrule",selectors:n,line:n[0].line,col:n[0].col}),this._readDeclarations(!0),this.fire({type:"endrule",selectors:n,line:n[0].line,col:n[0].col})),n},_selectors_group:function(){var e=this._tokenStream,t=[],n;n=this._selector();if(n!==null){t.push(n);while(e.match(Tokens.COMMA))this._readWhitespace(),n=this._selector(),n!==null?t.push(n):this._unexpectedToken(e.LT(1))}return t.length?t:null},_selector:function(){var e=this._tokenStream,t=[],n=null,r=null,i=null;n=this._simple_selector_sequence();if(n===null)return null;t.push(n);do{r=this._combinator();if(r!==null)t.push(r),n=this._simple_selector_sequence(),n===null?this._unexpectedToken(e.LT(1)):t.push(n);else{if(!this._readWhitespace())break;i=new Combinator(e.token().value,e.token().startLine,e.token().startCol),r=this._combinator(),n=this._simple_selector_sequence(),n===null?r!==null&&this._unexpectedToken(e.LT(1)):(r!==null?t.push(r):t.push(i),t.push(n))}}while(!0);return new Selector(t,t[0].line,t[0].col)},_simple_selector_sequence:function(){var e=this._tokenStream,t=null,n=[],r="",i=[function(){return e.match(Tokens.HASH)?new SelectorSubPart(e.token().value,"id",e.token().startLine,e.token().startCol):null},this._class,this._attrib,this._pseudo,this._negation],s=0,o=i.length,u=null,a=!1,f,l;f=e.LT(1).startLine,l=e.LT(1).startCol,t=this._type_selector(),t||(t=this._universal()),t!==null&&(r+=t);for(;;){if(e.peek()===Tokens.S)break;while(s1&&e.unget()),null)},_class:function(){var e=this._tokenStream,t;return e.match(Tokens.DOT)?(e.mustMatch(Tokens.IDENT),t=e.token(),new SelectorSubPart("."+t.value,"class",t.startLine,t.startCol-1)):null},_element_name:function(){var e=this._tokenStream,t;return e.match(Tokens.IDENT)?(t=e.token(),new SelectorSubPart(t.value,"elementName",t.startLine,t.startCol)):null},_namespace_prefix:function(){var e=this._tokenStream,t="";if(e.LA(1)===Tokens.PIPE||e.LA(2)===Tokens.PIPE)e.match([Tokens.IDENT,Tokens.STAR])&&(t+=e.token().value),e.mustMatch(Tokens.PIPE),t+="|";return t.length?t:null},_universal:function(){var e=this._tokenStream,t="",n;return n=this._namespace_prefix(),n&&(t+=n),e.match(Tokens.STAR)&&(t+="*"),t.length?t:null},_attrib:function(){var e=this._tokenStream,t=null,n,r;return e.match(Tokens.LBRACKET)?(r=e.token(),t=r.value,t+=this._readWhitespace(),n=this._namespace_prefix(),n&&(t+=n),e.mustMatch(Tokens.IDENT),t+=e.token().value,t+=this._readWhitespace(),e.match([Tokens.PREFIXMATCH,Tokens.SUFFIXMATCH,Tokens.SUBSTRINGMATCH,Tokens.EQUALS,Tokens.INCLUDES,Tokens.DASHMATCH])&&(t+=e.token().value,t+=this._readWhitespace(),e.mustMatch([Tokens.IDENT,Tokens.STRING]),t+=e.token().value,t+=this._readWhitespace()),e.mustMatch(Tokens.RBRACKET),new SelectorSubPart(t+"]","attribute",r.startLine,r.startCol)):null},_pseudo:function(){var e=this._tokenStream,t=null,n=":",r,i;return e.match(Tokens.COLON)&&(e.match(Tokens.COLON)&&(n+=":"),e.match(Tokens.IDENT)?(t=e.token().value,r=e.token().startLine,i=e.token().startCol-n.length):e.peek()==Tokens.FUNCTION&&(r=e.LT(1).startLine,i=e.LT(1).startCol-n.length,t=this._functional_pseudo()),t&&(t=new SelectorSubPart(n+t,"pseudo",r,i))),t},_functional_pseudo:function(){var e=this._tokenStream,t=null;return e.match(Tokens.FUNCTION)&&(t=e.token().value,t+=this._readWhitespace(),t+=this._expression(),e.mustMatch(Tokens.RPAREN),t+=")"),t},_expression:function(){var e=this._tokenStream,t="";while(e.match([Tokens.PLUS,Tokens.MINUS,Tokens.DIMENSION,Tokens.NUMBER,Tokens.STRING,Tokens.IDENT,Tokens.LENGTH,Tokens.FREQ,Tokens.ANGLE,Tokens.TIME,Tokens.RESOLUTION,Tokens.SLASH]))t+=e.token().value,t+=this._readWhitespace();return t.length?t:null},_negation:function(){var e=this._tokenStream,t,n,r="",i,s=null;return e.match(Tokens.NOT)&&(r=e.token().value,t=e.token().startLine,n=e.token().startCol,r+=this._readWhitespace(),i=this._negation_arg(),r+=i,r+=this._readWhitespace(),e.match(Tokens.RPAREN),r+=e.token().value,s=new SelectorSubPart(r,"not",t,n),s.args.push(i)),s},_negation_arg:function(){var e=this._tokenStream,t=[this._type_selector,this._universal,function(){return e.match(Tokens.HASH)?new SelectorSubPart(e.token().value,"id",e.token().startLine,e.token().startCol):null},this._class,this._attrib,this._pseudo],n=null,r=0,i=t.length,s,o,u,a;o=e.LT(1).startLine,u=e.LT(1).startCol;while(r0?new PropertyValue(n,n[0].line,n[0].col):null},_term:function(e){var t=this._tokenStream,n=null,r=null,i=null,s,o,u;return n=this._unary_operator(),n!==null&&(o=t.token().startLine,u=t.token().startCol),t.peek()==Tokens.IE_FUNCTION&&this.options.ieFilters?(r=this._ie_function(),n===null&&(o=t.token().startLine,u=t.token().startCol)):e&&t.match([Tokens.LPAREN,Tokens.LBRACE,Tokens.LBRACKET])?(s=t.token(),i=s.endChar,r=s.value+this._expr(e).text,n===null&&(o=t.token().startLine,u=t.token().startCol),t.mustMatch(Tokens.type(i)),r+=i,this._readWhitespace()):t.match([Tokens.NUMBER,Tokens.PERCENTAGE,Tokens.LENGTH,Tokens.ANGLE,Tokens.TIME,Tokens.FREQ,Tokens.STRING,Tokens.IDENT,Tokens.URI,Tokens.UNICODE_RANGE])?(r=t.token().value,n===null&&(o=t.token().startLine,u=t.token().startCol),this._readWhitespace()):(s=this._hexcolor(),s===null?(n===null&&(o=t.LT(1).startLine,u=t.LT(1).startCol),r===null&&(t.LA(3)==Tokens.EQUALS&&this.options.ieFilters?r=this._ie_function():r=this._function())):(r=s.value,n===null&&(o=s.startLine,u=s.startCol))),r!==null?new PropertyValuePart(n!==null?n+r:r,o,u):null},_function:function(){var e=this._tokenStream,t=null,n=null,r;if(e.match(Tokens.FUNCTION)){t=e.token().value,this._readWhitespace(),n=this._expr(!0),t+=n;if(this.options.ieFilters&&e.peek()==Tokens.EQUALS)do{this._readWhitespace()&&(t+=e.token().value),e.LA(0)==Tokens.COMMA&&(t+=e.token().value),e.match(Tokens.IDENT),t+=e.token().value,e.match(Tokens.EQUALS),t+=e.token().value,r=e.peek();while(r!=Tokens.COMMA&&r!=Tokens.S&&r!=Tokens.RPAREN)e.get(),t+=e.token().value,r=e.peek()}while(e.match([Tokens.COMMA,Tokens.S]));e.match(Tokens.RPAREN),t+=")",this._readWhitespace()}return t},_ie_function:function(){var e=this._tokenStream,t=null,n=null,r;if(e.match([Tokens.IE_FUNCTION,Tokens.FUNCTION])){t=e.token().value;do{this._readWhitespace()&&(t+=e.token().value),e.LA(0)==Tokens.COMMA&&(t+=e.token().value),e.match(Tokens.IDENT),t+=e.token().value,e.match(Tokens.EQUALS),t+=e.token().value,r=e.peek();while(r!=Tokens.COMMA&&r!=Tokens.S&&r!=Tokens.RPAREN)e.get(),t+=e.token().value,r=e.peek()}while(e.match([Tokens.COMMA,Tokens.S]));e.match(Tokens.RPAREN),t+=")",this._readWhitespace()}return t},_hexcolor:function(){var e=this._tokenStream,t=null,n;if(e.match(Tokens.HASH)){t=e.token(),n=t.value;if(!/#[a-f0-9]{3,6}/i.test(n))throw new SyntaxError("Expected a hex color but found '"+n+"' at line "+t.startLine+", col "+t.startCol+".",t.startLine,t.startCol);this._readWhitespace()}return t},_keyframes:function(){var e=this._tokenStream,t,n,r,i="";e.mustMatch(Tokens.KEYFRAMES_SYM),t=e.token(),/^@\-([^\-]+)\-/.test(t.value)&&(i=RegExp.$1),this._readWhitespace(),r=this._keyframe_name(),this._readWhitespace(),e.mustMatch(Tokens.LBRACE),this.fire({type:"startkeyframes",name:r,prefix:i,line:t.startLine,col:t.startCol}),this._readWhitespace(),n=e.peek();while(n==Tokens.IDENT||n==Tokens.PERCENTAGE)this._keyframe_rule(),this._readWhitespace(),n=e.peek();this.fire({type:"endkeyframes",name:r,prefix:i,line:t.startLine,col:t.startCol}),this._readWhitespace(),e.mustMatch(Tokens.RBRACE)},_keyframe_name:function(){var e=this._tokenStream,t;return e.mustMatch([Tokens.IDENT,Tokens.STRING]),SyntaxUnit.fromToken(e.token())},_keyframe_rule:function(){var e=this._tokenStream,t,n=this._key_list();this.fire({type:"startkeyframerule",keys:n,line:n[0].line,col:n[0].col}),this._readDeclarations(!0),this.fire({type:"endkeyframerule",keys:n,line:n[0].line,col:n[0].col})},_key_list:function(){var e=this._tokenStream,t,n,r=[];r.push(this._key()),this._readWhitespace();while(e.match(Tokens.COMMA))this._readWhitespace(),r.push(this._key()),this._readWhitespace();return r},_key:function(){var e=this._tokenStream,t;if(e.match(Tokens.PERCENTAGE))return SyntaxUnit.fromToken(e.token());if(e.match(Tokens.IDENT)){t=e.token();if(/from|to/i.test(t.value))return SyntaxUnit.fromToken(t);e.unget()}this._unexpectedToken(e.LT(1))},_skipCruft:function(){while(this._tokenStream.match([Tokens.S,Tokens.CDO,Tokens.CDC]));},_readDeclarations:function(e,t){var n=this._tokenStream,r;this._readWhitespace(),e&&n.mustMatch(Tokens.LBRACE),this._readWhitespace();try{for(;;){if(!(n.match(Tokens.SEMICOLON)||t&&this._margin())){if(!this._declaration())break;if(!n.match(Tokens.SEMICOLON))break}this._readWhitespace()}n.mustMatch(Tokens.RBRACE),this._readWhitespace()}catch(i){if(!(i instanceof SyntaxError&&!this.options.strict))throw i;this.fire({type:"error",error:i,message:i.message,line:i.line,col:i.col}),r=n.advance([Tokens.SEMICOLON,Tokens.RBRACE]);if(r==Tokens.SEMICOLON)this._readDeclarations(!1,t);else if(r!=Tokens.RBRACE)throw i}},_readWhitespace:function(){var e=this._tokenStream,t="";while(e.match(Tokens.S))t+=e.token().value;return t},_unexpectedToken:function(e){throw new SyntaxError("Unexpected token '"+e.value+"' at line "+e.startLine+", col "+e.startCol+".",e.startLine,e.startCol)},_verifyEnd:function(){this._tokenStream.LA(1)!=Tokens.EOF&&this._unexpectedToken(this._tokenStream.LT(1))},_validateProperty:function(e,t){Validation.validate(e,t)},parse:function(e){this._tokenStream=new TokenStream(e,Tokens),this._stylesheet()},parseStyleSheet:function(e){return this.parse(e)},parseMediaQuery:function(e){this._tokenStream=new TokenStream(e,Tokens);var t=this._media_query();return this._verifyEnd(),t},parsePropertyValue:function(e){this._tokenStream=new TokenStream(e,Tokens),this._readWhitespace();var t=this._expr();return this._readWhitespace(),this._verifyEnd(),t},parseRule:function(e){this._tokenStream=new TokenStream(e,Tokens),this._readWhitespace();var t=this._ruleset();return this._readWhitespace(),this._verifyEnd(),t},parseSelector:function(e){this._tokenStream=new TokenStream(e,Tokens),this._readWhitespace();var t=this._selector();return this._readWhitespace(),this._verifyEnd(),t},parseStyleAttribute:function(e){e+="}",this._tokenStream=new TokenStream(e,Tokens),this._readDeclarations()}};for(t in n)n.hasOwnProperty(t)&&(e[t]=n[t]);return e}();var Properties={"align-items":"flex-start | flex-end | center | baseline | stretch","align-content":"flex-start | flex-end | center | space-between | space-around | stretch","align-self":"auto | flex-start | flex-end | center | baseline | stretch","-webkit-align-items":"flex-start | flex-end | center | baseline | stretch","-webkit-align-content":"flex-start | flex-end | center | space-between | space-around | stretch","-webkit-align-self":"auto | flex-start | flex-end | center | baseline | stretch","alignment-adjust":"auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | | ","alignment-baseline":"baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",animation:1,"animation-delay":{multi:"