From 5683416bdf68eba2ae7eade071d6951d1881a1b1 Mon Sep 17 00:00:00 2001 From: Attila Buti Date: Mon, 26 Mar 2018 13:26:22 +0200 Subject: [PATCH] Releasing 1.2.0 --- CHANGELOG.md | 9 ++ README.md | 92 ++++++++++---- media/preview_icon_dark.svg | 2 +- media/preview_icon_light.svg | 2 +- package-lock.json | 191 +++++++++++++++++------------ package.json | 225 +++++++++++++++++++---------------- src/email.ts | 166 +++++++++++++++++++++----- src/export.ts | 10 +- src/screenshot.ts | 22 +++- 9 files changed, 491 insertions(+), 228 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e14a7..da41e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to the "mjml" extension will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +### [1.2.0] (2018-03-26) +* [new] Configuration property `mjml.mailer`: send email with Nodemailer or Mailjet. Possible values are 'nodemailer' and 'mailjet'. +* [new] Configuration property `mjml.nodemailer`: Nodemailer configuration. Please see the [Nodemailer](https://nodemailer.com) documentation for more information. +* Send email with Nodemailer. +* Added support for inline images (automatically generated from local images). +* New preview icon. +* Some other improvements. +* MJML 4.0.3 + ### [1.1.0] (2018-03-18) * [new] Configuration property `mjml.exportType`: Specifies the file type of the output file. * `MJML: Export HTML`: allows to specify the exported file type (e.g. example.html or .pug). diff --git a/README.md b/README.md index 86b777a..a0a125a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ MJML preview, lint, compile for Visual Studio Code. [![GitHub license][license-img]][license-url] [![Visual Studio Marketplace][vs-market-version]][vs-market-url] [![Visual Studio Marketplace installs][vs-market-installs]][vs-market-url] +[![Dependencies Status][dependencies-status]][dependencies-status] ## Features @@ -12,7 +13,7 @@ MJML preview, lint, compile for Visual Studio Code. * Export HTML file from MJML. * Copy the result HTML to clipboard. * Take a screenshot of the rendered MJML document. -* Send email with Mailjet. +* Send email with Nodemailer or Mailjet. * Code snippets for MJML. Based on [mjml-syntax](https://github.com/mjmlio/mjml-syntax). * Fetch official templates. Based on [mjml-app](https://github.com/mjmlio/mjml-app). * Beautify MJML code. @@ -35,38 +36,40 @@ Start command palette (with `Ctrl+Shift+P` or `F1`) and start typing `MJML`. ## Available commands The following command is available: +* **MJML: Beautify** or **Format Document** Beautify MJML code. +* **MJML: Copy HTML** Copy the result HTML to clipboard. * **MJML: Export HTML** Export HTML file from MJML. +* **MJML: Migrate** Migrate a template from MJML 3 to MJML 4. +* **MJML: Multiple Screenshots** Take multiple screenshots of the rendered MJML document. * **MJML: Open Preview to the Side** Opens a preview in a column alongside the current document. * **MJML: Screenshot** Take a screenshot of the rendered MJML document, and save it as a file. -* **MJML: Multiple Screenshots** Take multiple screenshots of the rendered MJML document. -* **MJML: Copy HTML** Copy the result HTML to clipboard. -* **MJML: Send Email** Send email with Mailjet. +* **MJML: Send Email** Send email with Nodemailer or Mailjet. * **MJML: Template** Fetch official templates. -* **MJML: Beautify** or **Format Document** Beautify MJML code. -* **MJML: Migrate** Migrate a template from MJML 3 to MJML 4. ## Settings | Name | Default | Description | | --- | --- | --- | +| `mjml.beautifyHtmlOutput` | `false` | Beautify HTML output. (Works when `mjml.minifyHtmlOutput` aren't enabled.) | +| `mjml.beautify` | ` ` | Beautify options ([available options](https://github.com/beautify-web/js-beautify#options)). | +| `mjml.exportType` | `.html` | Specifies the file type of the output file. | | `mjml.lintEnable` | `true` | Enable/disable MJML linter (requires restart). | | `mjml.lintWhenTyping` | `true` | Whether the linter is run on type or on save. | -| `mjml.updateWhenTyping` | `true` | Update preview when typing. | -| `mjml.preserveFocus` | `true` | Preserve focus of Text Editor after preview open. | +| `mjml.mailFromName` | ` ` | Sender name. | +| `mjml.mailRecipients` | ` ` | Comma separated list of recipients email addresses. | +| `mjml.mailSender` | ` ` | Sender email address. (Mailjet: must be a verified sender.) | +| `mjml.mailSubject` | ` ` | Email subject. | +| `mjml.mailer` | `mailjet` | Send email with Nodemailer or Mailjet. Possible values are 'nodemailer' and 'mailjet'. | +| `mjml.mailjetAPIKey` | ` ` | Mailjet API Key. | +| `mjml.mailjetAPISecret` | ` ` | Mailjet API Secret. | | `mjml.minifyHtmlOutput` | `true` | Minify HTML output. | -| `mjml.beautifyHtmlOutput` | `false` | Beautify HTML output. (Works when `mjml.minifyHtmlOutput` aren't enabled.) | +| `mjml.nodemailer` | `{}` | Nodemailer configuration. Please see the [Nodemailer](https://nodemailer.com) documentation for more information. | +| `mjml.preserveFocus` | `true` | Preserve focus of Text Editor after preview open. | +| `mjml.screenshotQuality` | `75` | Screenshot quality. | +| `mjml.screenshotType` | `jpg` | Screenshot type. Possible values are 'png', 'jpg', and 'jpeg'. | | `mjml.screenshotWidth` | `650` | Screenshot width. | | `mjml.screenshotWidths` | `640,750` | Screenshot widths. | -| `mjml.screenshotType` | `jpg` | Screenshot type. Possible values are 'png', 'jpg', and 'jpeg'. | -| `mjml.screenshotQuality` | `75` | Screenshot quality. | -| `mjml.mailjetAPIKey` | ` ` | Mailjet API Key. | -| `mjml.mailjetAPISecret` | ` ` | Mailjet API Secret. | -| `mjml.mailSender` | ` ` | Sender email address. (Mailjet: must be a verified sender.) | -| `mjml.mailFromName` | ` ` | Sender name. | -| `mjml.mailSubject` | ` ` | Email subject. | -| `mjml.mailRecipients` | ` ` | Comma separated list of recipients email addresses. | -| `mjml.beautify` | ` ` | Beautify options ([available options](https://github.com/beautify-web/js-beautify#options)). | -| `mjml.exportType` | `.html` | Specifies the file type of the output file. | +| `mjml.updateWhenTyping` | `true` | Update preview when typing. | ## Snippets @@ -107,8 +110,56 @@ The following command is available: | `mjlink` | [mj-link](https://mjml.io/documentation/#mjml-navbar) | `` | | `mjml-` | | Basic MJML Template | +## Nodemailer configuration + +Please see the [Nodemailer](https://nodemailer.com) documentation for more information. + +### [Gmail](https://gmail.com) +```json +"mjml.nodemailer": { + "service": "Gmail", + "auth": { + "user": "youremail@gmail.com", + "pass": "password" + } +} +``` + +### [Mailtrap](https://mailtrap.io) +```json +"mjml.nodemailer": { + "host": "smtp.mailtrap.io", + "port": 2525, + "auth": { + "user": "username", + "pass": "password" + } +} +``` + +### [Ethereal](https://ethereal.email) +```json +"mjml.nodemailer": { + "host": "smtp.ethereal.email", + "port": 587, + "auth": { + "user": "youremail@ethereal.email", + "pass": "password" + } +} +``` + ## Change Log +### [1.2.0] (2018-03-26) +* [new] Configuration property `mjml.mailer`: send email with Nodemailer or Mailjet. Possible values are 'nodemailer' and 'mailjet'. +* [new] Configuration property `mjml.nodemailer`: Nodemailer configuration. Please see the [Nodemailer](https://nodemailer.com) documentation for more information. +* Send email with Nodemailer. +* Added support for inline images (automatically generated from local images). +* New preview icon. +* Some other improvements. +* MJML 4.0.3 + ### [1.1.0] (2018-03-18) * [new] Configuration property `mjml.exportType`: Specifies the file type of the output file. * `MJML: Export HTML`: allows to specify the exported file type (e.g. example.html or .pug). @@ -201,4 +252,5 @@ This extension is licensed under the [MIT License][license-url]. [license-url]: https://raw.githubusercontent.com/attilabuti/vscode-mjml/master/LICENSE [vs-market-version]: https://vsmarketplacebadge.apphb.com/version-short/attilabuti.vscode-mjml.svg?style=flat-square [vs-market-installs]: https://vsmarketplacebadge.apphb.com/installs/attilabuti.vscode-mjml.svg?style=flat-square -[vs-market-url]: https://marketplace.visualstudio.com/items?itemName=attilabuti.vscode-mjml \ No newline at end of file +[vs-market-url]: https://marketplace.visualstudio.com/items?itemName=attilabuti.vscode-mjml +[dependencies-status]: https://david-dm.org/attilabuti/vscode-mjml/status.svg?style=flat-square \ No newline at end of file diff --git a/media/preview_icon_dark.svg b/media/preview_icon_dark.svg index cb42d8a..c993d90 100644 --- a/media/preview_icon_dark.svg +++ b/media/preview_icon_dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/media/preview_icon_light.svg b/media/preview_icon_light.svg index 9d0ed0c..c993d90 100644 --- a/media/preview_icon_light.svg +++ b/media/preview_icon_light.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 622abf4..1e33541 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-mjml", - "version": "1.0.0", + "version": "1.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,18 +10,46 @@ "integrity": "sha1-p9RUyeHkVCMo9/Huz1Mzvoz7UO0=", "dev": true }, + "@types/events": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "dev": true + }, "@types/file-url": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/file-url/-/file-url-2.0.0.tgz", "integrity": "sha512-9YqUM3izkmwtCbq6ANdcHJiU2mpDvPm3WuHOcJ5ZouHw7CoYcyY/KvZm6dmYTIurfrRsmBIvHr6y1jpBjDd4Jg==", "dev": true }, + "@types/is-url": { + "version": "1.2.28", + "resolved": "https://registry.npmjs.org/@types/is-url/-/is-url-1.2.28.tgz", + "integrity": "sha1-kU2r1QVG2bAUKAbkLHK8fCt+B4c=", + "dev": true + }, + "@types/mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", + "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==", + "dev": true + }, "@types/node": { - "version": "9.4.7", - "resolved": "http://registry.npmjs.org/@types/node/-/node-9.4.7.tgz", - "integrity": "sha512-4Ba90mWNx8ddbafuyGGwjkZMigi+AWfYLSDCpovwsE63ia8w93r3oJ8PIAQc3y8U+XHcnMOHPIzNe3o438Ywcw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.0.tgz", + "integrity": "sha512-h3YZbOq2+ZoDFI1z8Zx0Ck/xRWkOESVaLdgLdd/c25mMQ1Y2CAkILu9ny5A15S5f32gGcQdaUIZ2jzYr8D7IFg==", "dev": true }, + "@types/nodemailer": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-4.6.0.tgz", + "integrity": "sha512-DWG172izmWXfShUm2Lm6nVght5TxDI1cx2cKiGPeu2f66yTnn0QY7WhC9OSybdPoxGu7UbRXNuIkIzB+NE648A==", + "dev": true, + "requires": { + "@types/events": "1.2.0", + "@types/node": "9.6.0" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -458,9 +486,9 @@ } }, "commander": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.0.tgz", - "integrity": "sha512-7B1ilBwtYSbetCgTY1NJFg+gVpestg0fdA1MhC1Vs4ssyfSXnCAjFr+QcQM9/RedXC0EaUx1sG8Smgw2VfgKEg==" + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, "component-emitter": { "version": "1.2.1", @@ -770,7 +798,7 @@ "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", "requires": { "bluebird": "3.5.1", - "commander": "2.15.0", + "commander": "2.15.1", "lru-cache": "3.2.0", "semver": "5.5.0", "sigmund": "1.0.1" @@ -1067,9 +1095,9 @@ } }, "formidable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.0.tgz", - "integrity": "sha512-hr9aT30rAi7kf8Q2aaTpSP7xGMhlJ+MdrUDVZs3rxbD3L/K46A86s2VY7qC2D2kGYGBtiT/3j6wTx1eeUq5xAQ==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" }, "from": { "version": "0.1.7", @@ -1447,9 +1475,9 @@ "dev": true }, "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "clone-stats": { @@ -1485,7 +1513,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "commander": "2.15.0", + "commander": "2.15.1", "is-my-json-valid": "2.17.2", "pinkie-promise": "2.0.1" } @@ -1574,7 +1602,7 @@ "integrity": "sha1-CjcT2NTpIhxY8QyhbAEWyeJe2nw=", "dev": true, "requires": { - "clone": "1.0.3", + "clone": "1.0.4", "clone-buffer": "1.0.0", "clone-stats": "1.0.0", "cloneable-readable": "1.1.2", @@ -1599,9 +1627,9 @@ }, "dependencies": { "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "replace-ext": { @@ -1625,7 +1653,7 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "1.0.3", + "clone": "1.0.4", "clone-stats": "0.0.1", "replace-ext": "0.0.1" } @@ -1684,9 +1712,9 @@ }, "dependencies": { "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "minimist": { @@ -1713,7 +1741,7 @@ "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", "dev": true, "requires": { - "clone": "1.0.3", + "clone": "1.0.4", "clone-stats": "0.0.1", "replace-ext": "0.0.1" } @@ -1736,9 +1764,9 @@ }, "dependencies": { "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, "clone-stats": { @@ -1762,7 +1790,7 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "2.1.1", + "clone": "2.1.2", "clone-buffer": "1.0.0", "clone-stats": "1.0.0", "cloneable-readable": "1.1.2", @@ -1855,18 +1883,18 @@ "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" }, "html-minifier": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.11.tgz", - "integrity": "sha512-kIi9C090qWW5cGxEf+EwNUczduyVR6krk29WB3zDSWBQN6xuh/1jCXgmY4SvqzaJMOZFCnf8wcNzA8iPsfLiUQ==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.12.tgz", + "integrity": "sha512-+N778qLf0RWBscD0TPGoYdeGNDZ0s76/0pQhY1/409EOudcENkm9IbSkk37RDyPdg/09GVHTKotU4ya93RF1Gg==", "requires": { "camel-case": "3.0.0", "clean-css": "4.1.11", - "commander": "2.15.0", + "commander": "2.15.1", "he": "1.1.1", "ncname": "1.0.0", "param-case": "2.1.1", "relateurl": "0.2.7", - "uglify-js": "3.3.15" + "uglify-js": "3.3.16" } }, "htmlparser2": { @@ -2140,6 +2168,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-url": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.3.tgz", + "integrity": "sha512-vmOHLvzbcnsdFz8wQPXj1lgI5SE8AUlUGMenzuZzRFjoReb1WB+pLt9GrIo7BTker+aTcwrjTDle7odioWeqyw==" + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -2624,9 +2657,9 @@ } }, "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.2.0.tgz", + "integrity": "sha512-0Qz9uF1ATtl8RKJG4VRfOymh7PyEor6NbrI/61lRfuRe4vx9SNATrvAeTj2EWVRKjEQGskrzWkJBBY5NbaVHIA==" }, "mime-db": { "version": "1.33.0", @@ -2665,16 +2698,16 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mjml": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.0.2.tgz", - "integrity": "sha512-ffZ/vHtdTwS/G++IRB2Ukb1PeleV6+BhGZ2+K7wQKMiNMtdaIN+ivo2rgkQQCehmnjDZTci2b4jIl6qSdH5Msw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.0.3.tgz", + "integrity": "sha512-j6I+o+NWaOanuslRMOZ7syj1vDnJWU7AX1q7EmvU62t1g1OKRhSGqMuCE0eJWFifpFxMue/AeU2n+ITkex5LSQ==", "requires": { "mjml-accordion": "4.0.2", "mjml-body": "4.0.2", "mjml-button": "4.0.1", "mjml-carousel": "4.0.2", - "mjml-cli": "4.0.1", - "mjml-column": "4.0.1", + "mjml-cli": "4.0.3", + "mjml-column": "4.0.3", "mjml-core": "4.0.1", "mjml-divider": "4.0.1", "mjml-group": "4.0.2", @@ -2686,15 +2719,15 @@ "mjml-head-style": "4.0.1", "mjml-head-title": "4.0.1", "mjml-hero": "4.0.1", - "mjml-image": "4.0.2", + "mjml-image": "4.0.3", "mjml-migrate": "4.0.1", - "mjml-navbar": "4.0.2", + "mjml-navbar": "4.0.3", "mjml-raw": "4.0.2", "mjml-section": "4.0.1", "mjml-social": "4.0.2", "mjml-spacer": "4.0.1", "mjml-table": "4.0.1", - "mjml-text": "4.0.1", + "mjml-text": "4.0.3", "mjml-validator": "4.0.1", "mjml-wrapper": "4.0.1" } @@ -2736,9 +2769,9 @@ } }, "mjml-cli": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.0.1.tgz", - "integrity": "sha512-BPgJsQk9TE18mgyXQbIB1X0xT6EILS6cDmZHAe7aqGKBlASa1bPrmJ4uD0C0PimA+cOZgGYKy44a+dLjtpBSnw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.0.3.tgz", + "integrity": "sha512-RiGVZTeXotX+ymQrGKIxwahAvKbRzavC0Xxm9Q3lY+5RvGmOgVRIcSJUXVNlgtwLQtCduy41i8qSBvIj23Awqw==", "requires": { "babel-runtime": "6.26.0", "chokidar": "1.7.0", @@ -2753,9 +2786,9 @@ } }, "mjml-column": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.0.1.tgz", - "integrity": "sha512-QjCAp44zEZabDf4SdT5ytBS5COMorJh3nqUvzGUCg5bBcCcs4f5QfZZTuZqCp3zlYzpuiJOrHyZ4Wtide/ItSw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.0.3.tgz", + "integrity": "sha512-pspklS+HK8XyT7bTknvd9XzdSqXEUJcf/vxN8D/2wbuFfuJGKjbO2czCU8WQLy+zltLhfKueypb/0dgicRpgGQ==", "requires": { "lodash": "4.17.5", "mjml-core": "4.0.1" @@ -2766,7 +2799,7 @@ "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.0.1.tgz", "integrity": "sha512-ey7exKkb8mhK/Iv/mWVy6ZkLe/LVDijdCzbbV9WhDau7TnxVGLZg4VgjkwYfTc5nOuuwCZfTcSzS/q5r7gpjSg==", "requires": { - "html-minifier": "3.5.11", + "html-minifier": "3.5.12", "js-beautify": "1.7.5", "juice": "4.2.3", "lodash": "4.17.5", @@ -2866,9 +2899,9 @@ } }, "mjml-image": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.0.2.tgz", - "integrity": "sha512-84ZWzw45V7x0F4BogGdir2WsPj0ERjmyoYm9E2mryk7C+TRYRE+upvi96RmQnaIZvJcRoX+p7P4VTwopstGXrA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.0.3.tgz", + "integrity": "sha512-bQt4vk7SNOkmr0PDmI3fBJ+2oRnBI0r9w0+hFY0UVPrl3CdXK/gP/dn0zwT0ll3b+NrlZ94z32qSRlQVGa/jVw==", "requires": { "lodash": "4.17.5", "mjml-core": "4.0.1" @@ -2879,15 +2912,15 @@ "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.0.1.tgz", "integrity": "sha512-LGO+EwrvhpXDFW8tm3y+IKY06goyDimTBZamb2m67SMIBo338z00jwOsUOUZ1hHEdKhguRszXqu0PldZH8quKw==", "requires": { - "commander": "2.15.0", + "commander": "2.15.1", "mjml-core": "4.0.1", "mjml-parser-xml": "4.0.1" } }, "mjml-navbar": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.0.2.tgz", - "integrity": "sha512-3GebaVCabeirYV03lvaKmOXlhlimtYDXUDjYHTXZCbO7c604HqhaEiXyMh3qPC4YxpP/NWdN0dvWRp3OgUCVZg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.0.3.tgz", + "integrity": "sha512-lqFzLZKJE9RMXCMT0h0M5hKPZaHdfd8Hnc29n1qRDzDIzmywSdWgpJXx8DZ8spjAA+sE5msnhzfa9rKdoPWQHw==", "requires": { "lodash": "4.17.5", "mjml-core": "4.0.1" @@ -2948,9 +2981,9 @@ } }, "mjml-text": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.0.1.tgz", - "integrity": "sha512-WAgJ9tAqFbNJvXVfI1/CQRo4AZvAaO0ftUWzPRbkz6jO2pHvQAFr/EsJZP5PJkm+HuLUvvhfVJrvQ59fWu83yQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.0.3.tgz", + "integrity": "sha512-SXJuBy7WsDIF6lp/xMsRdMm5UTAgHVvF+dKt/MLtTTH8wfMZTXTEZ76siQfH0C/5BaemJNswLcsma20c7Y8OMg==", "requires": { "lodash": "4.17.5", "mjml-core": "4.0.1" @@ -3097,6 +3130,11 @@ "is": "3.2.1" } }, + "nodemailer": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.6.3.tgz", + "integrity": "sha512-1AmOpDZJtyPAO+gfUBfT+MWHbYwQ+DZvb1gvYaTxBZV/lUeysZIt4kDq8Dlwt6ViUZGp3cMGR+D1MNQYyYiVUg==" + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -3998,11 +4036,18 @@ "debug": "3.1.0", "extend": "3.0.1", "form-data": "2.3.2", - "formidable": "1.2.0", + "formidable": "1.2.1", "methods": "1.1.2", "mime": "1.6.0", "qs": "6.5.1", "readable-stream": "2.3.5" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + } } }, "superagent-proxy": { @@ -4150,11 +4195,11 @@ "dev": true }, "uglify-js": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.15.tgz", - "integrity": "sha512-bqtBCAINYXX/OkdnqMGpbXr+OPWc00hsozRpk+dAtfnbdk2jjKiLmyOkQ7zamg648lVMnzATL8JrSN6LmaVpYA==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.16.tgz", + "integrity": "sha512-FMh5SRqJRGhv9BbaTffENIpDDQIoPDR8DBraunGORGhySArsXlw9++CN+BWzPBLpoI4RcSnpfGPnilTxWL3Vvg==", "requires": { - "commander": "2.15.0", + "commander": "2.15.1", "source-map": "0.6.1" }, "dependencies": { @@ -4271,9 +4316,9 @@ }, "dependencies": { "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "replace-ext": { @@ -4297,7 +4342,7 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "1.0.3", + "clone": "1.0.4", "clone-stats": "0.0.1", "replace-ext": "0.0.1" } @@ -4315,9 +4360,9 @@ } }, "vscode": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.13.tgz", - "integrity": "sha512-WpHxNfZoxT/JjJbfNNTmXHV9up03yjo3I+jZcYvDvukvYgHtNsvAC98RLz18qmoLwJTncEKrLoxwd4A+VHMmlA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.14.tgz", + "integrity": "sha512-acfn3fzGtTm7UjChAN7/YjsC0qIyJeuSrJwvm6qb7tLN6Geq1FmCz1JnBOc3kaY+HCLjQBAfwG/CsgnasOdXMw==", "dev": true, "requires": { "glob": "7.1.2", diff --git a/package.json b/package.json index ee77322..74e11b5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-mjml", "displayName": "MJML", "description": "MJML preview, lint, compile for Visual Studio Code.", - "version": "1.1.0", + "version": "1.2.0", "publisher": "attilabuti", "license": "MIT", "readme": "README.md", @@ -41,15 +41,15 @@ "vscode": "^1.6.0" }, "activationEvents": [ - "onCommand:mjml.previewToSide", - "onCommand:mjml.exportHTML", + "onCommand:mjml.beautify", "onCommand:mjml.copyHTML", - "onCommand:mjml.screenshot", + "onCommand:mjml.exportHTML", + "onCommand:mjml.migrate", "onCommand:mjml.multipleScreenshots", + "onCommand:mjml.previewToSide", + "onCommand:mjml.screenshot", "onCommand:mjml.sendEmail", "onCommand:mjml.template", - "onCommand:mjml.beautify", - "onCommand:mjml.migrate", "onLanguage:mjml" ], "main": "./out/extension", @@ -58,105 +58,116 @@ "type": "object", "title": "MJML", "properties": { - "mjml.lintEnable": { - "type": "boolean", - "default": true, - "description": "Enable/disable MJML linter (requires restart)." + "mjml.beautify": { + "default": { + "indent_size": 2, + "wrap_attributes_indent_size": 2 + }, + "description": "Beautify options.", + "type": "object" }, - "mjml.lintWhenTyping": { - "type": "boolean", - "default": true, - "description": "Whether the linter is run on type or on save." + "mjml.beautifyHtmlOutput": { + "default": false, + "description": "Beautify HTML output. (Works when `mjml.minifyHtmlOutput` aren't enabled.)", + "type": "boolean" }, - "mjml.updateWhenTyping": { - "type": "boolean", - "default": true, - "description": "Update preview when typing." + "mjml.exportType": { + "default": ".html", + "description": "Specifies the file type of the output file.", + "type": "string" }, - "mjml.preserveFocus": { - "type": "boolean", + "mjml.lintEnable": { "default": true, - "description": "Preserve focus of Text Editor after preview open." + "description": "Enable/disable MJML linter (requires restart).", + "type": "boolean" }, - "mjml.minifyHtmlOutput": { - "type": "boolean", + "mjml.lintWhenTyping": { "default": true, - "description": "Minify HTML output." + "description": "Whether the linter is run on type or on save.", + "type": "boolean" }, - "mjml.beautifyHtmlOutput": { - "type": "boolean", - "default": false, - "description": "Beautify HTML output. (Works when `mjml.minifyHtmlOutput` aren't enabled.)" + "mjml.mailFromName": { + "default": "", + "description": "Sender name.", + "type": "string" }, - "mjml.screenshotWidth": { - "type": "number", - "default": 650, - "description": "Screenshot width." + "mjml.mailRecipients": { + "default": "", + "description": "Comma separated list of recipients email addresses.", + "type": "string" }, - "mjml.screenshotWidths": { - "type": "array", - "default": [ - 640, - 750 - ], - "description": "Screenshot widths." + "mjml.mailSender": { + "default": "", + "description": "Sender email address. (Mailjet: must be a verified sender.)", + "type": "string" }, - "mjml.screenshotType": { - "type": "string", - "default": "jpg", - "description": "Screenshot type. Possible values are 'png', 'jpg', and 'jpeg'." + "mjml.mailSubject": { + "default": "", + "description": "Email subject.", + "type": "string" }, - "mjml.screenshotQuality": { - "type": "number", - "default": 75, - "description": "Screenshot quality." + "mjml.mailer": { + "default": "mailjet", + "description": "Send email with Nodemailer or Mailjet. Possible values are 'nodemailer' and 'mailjet'.", + "type": "string" }, "mjml.mailjetAPIKey": { - "type": "string", "default": "", - "description": "Mailjet API Key." + "description": "Mailjet API Key.", + "type": "string" }, "mjml.mailjetAPISecret": { - "type": "string", "default": "", - "description": "Mailjet API Secret." + "description": "Mailjet API Secret.", + "type": "string" }, - "mjml.mailSender": { - "type": "string", - "default": "", - "description": "Sender email address. (Mailjet: must be a verified sender.)" + "mjml.minifyHtmlOutput": { + "default": true, + "description": "Minify HTML output.", + "type": "boolean" }, - "mjml.mailFromName": { - "type": "string", - "default": "", - "description": "Sender name." + "mjml.nodemailer": { + "default": {}, + "description": "Nodemailer configuration. Please see the Nodemailer (https://nodemailer.com) documentation for more information.", + "type": "object" }, - "mjml.mailSubject": { - "type": "string", - "default": "", - "description": "Email subject." + "mjml.preserveFocus": { + "default": true, + "description": "Preserve focus of Text Editor after preview open.", + "type": "boolean" }, - "mjml.mailRecipients": { - "type": "string", - "default": "", - "description": "Comma separated list of recipients email addresses." + "mjml.screenshotQuality": { + "default": 75, + "description": "Screenshot quality.", + "type": "number" }, - "mjml.beautify": { - "type": "object", - "default": { - "indent_size": 2, - "wrap_attributes_indent_size": 2 - }, - "description": "Beautify options." + "mjml.screenshotType": { + "default": "jpg", + "description": "Screenshot type. Possible values are 'png', 'jpg', and 'jpeg'.", + "type": "string" }, - "mjml.exportType": { - "type": "string", - "default": ".html", - "description": "Specifies the file type of the output file." + "mjml.screenshotWidth": { + "default": 650, + "description": "Screenshot width.", + "type": "number" + }, + "mjml.screenshotWidths": { + "default": [ + 640, + 750 + ], + "description": "Screenshot widths.", + "type": "array" + }, + "mjml.updateWhenTyping": { + "default": true, + "description": "Update preview when typing.", + "type": "boolean" } } }, - "commands": [{ + "commands": [ + { "command": "mjml.previewToSide", "title": "Open Preview to the Side", "category": "MJML", @@ -206,25 +217,31 @@ "category": "MJML" } ], - "languages": [{ - "id": "mjml", - "extensions": [ - ".mjml" - ], - "aliases": [ - "mjml" - ] - }], - "snippets": [{ - "language": "mjml", - "path": "./snippets/mjml.json" - }], + "languages": [ + { + "id": "mjml", + "extensions": [ + ".mjml" + ], + "aliases": [ + "mjml" + ] + } + ], + "snippets": [ + { + "language": "mjml", + "path": "./snippets/mjml.json" + } + ], "menus": { - "editor/title": [{ - "when": "editorLangId == mjml", - "command": "mjml.previewToSide", - "group": "navigation" - }] + "editor/title": [ + { + "when": "editorLangId == mjml", + "command": "mjml.previewToSide", + "group": "navigation" + } + ] } }, "scripts": { @@ -236,17 +253,23 @@ "devDependencies": { "@types/copy-paste": "1.1.30", "@types/file-url": "2.0.0", + "@types/is-url": "^1.2.28", + "@types/mime": "^2.0.0", "@types/node": "*", + "@types/nodemailer": "^4.6.0", "typescript": "^2.7.2", "vscode": "^1.0.3" }, "dependencies": { "copy-paste": "^1.3.0", "file-url": "^2.0.2", - "mjml": "^4.0.2", + "is-url": "^1.2.3", + "js-beautify": "^1.7.5", + "mime": "^2.2.0", + "mjml": "^4.0.3", "node-fetch": "2.1.1", "node-mailjet": "^3.2.1", - "webshot": "^0.18.0", - "js-beautify": "^1.7.5" + "nodemailer": "^4.6.3", + "webshot": "^0.18.0" } -} \ No newline at end of file +} diff --git a/src/email.ts b/src/email.ts index 23e3425..9267644 100644 --- a/src/email.ts +++ b/src/email.ts @@ -1,8 +1,13 @@ "use strict"; import * as vscode from "vscode"; +import * as path from "path"; +import * as fs from "fs"; import * as mailjet from "node-mailjet"; +import * as nodemailer from "nodemailer"; +import * as mime from "mime"; +import isUrl = require("is-url"); import helper from "./helper"; @@ -11,47 +16,152 @@ export default class SendEmail { constructor(subscriptions: vscode.Disposable[]) { subscriptions.push( vscode.commands.registerCommand("mjml.sendEmail", () => { - this.sendEmail(); + helper.renderMJML((content: string) => { + this.sendEmail(content, vscode.window.activeTextEditor.document.uri.fsPath); + }); }) ); } - private sendEmail(): void { - helper.renderMJML((content: string) => { - this.sendEmailWithMailjet(content); - }); - } + private sendEmail(content: string, mjmlPath: string): void { + let mailer: string = vscode.workspace.getConfiguration("mjml").mailer.toLowerCase(); - private sendEmailWithMailjet(content: string): void { let defaultSubject: string = vscode.workspace.getConfiguration("mjml").mailSubject; let defaultRecipients: string = vscode.workspace.getConfiguration("mjml").mailRecipients; - vscode.window.showInputBox({ placeHolder: `Subject (${defaultSubject})` }).then((subject: string) => { - vscode.window.showInputBox({ placeHolder: `Recipients (${defaultRecipients})` }).then((recipients: string) => { - if (!subject) { - subject = defaultSubject; + vscode.window.showInputBox({ + prompt: "Subject", + placeHolder: "Type a subject for the email.", + value: defaultSubject + }).then((subject: string) => { + if (!subject) { + return; + } + + vscode.window.showInputBox({ + prompt: "Recipients", + placeHolder: "Comma-separated list of recipients.", + value: defaultRecipients + }).then((recipients: string) => { + if (!recipients) { + return; } - let recipientList: Array<{ Email: string }> = (recipients ? recipients : defaultRecipients).replace(/\s/g, "").split(",").map((emailAddress: string) => { - return { Email: emailAddress }; - }); + recipients = (recipients ? recipients : defaultRecipients).replace(/\s/g, ""); - mailjet.connect( - vscode.workspace.getConfiguration("mjml").mailjetAPIKey, - vscode.workspace.getConfiguration("mjml").mailjetAPISecret - ).post("send").request({ - FromEmail: vscode.workspace.getConfiguration("mjml").mailSender, - FromName: vscode.workspace.getConfiguration("mjml").mailFromName, - Subject: subject, - Recipients: recipientList, - "Html-part": content - }).then((result: object) => { - vscode.window.showInformationMessage("Mail has been sent successfully."); - }).catch((err: any) => { - vscode.window.showErrorMessage("Something went wrong."); - }); + let attachments: any[] = this.createAttachments(content, mjmlPath, mailer); + content = this.replaceImages(content, attachments, mailer); + + if (mailer == 'nodemailer') { + this.sendEmailWithNodemailer(subject, recipients, content, attachments); + } + else { + this.sendEmailWithMailjet(subject, recipients, content, attachments); + } }); }); } + private sendEmailWithNodemailer(subject: string, recipients: string, content: string, attachments: any[]): void { + let transportOptions: any = vscode.workspace.getConfiguration("mjml").nodemailer; + + nodemailer.createTransport(transportOptions).sendMail({ + from: vscode.workspace.getConfiguration("mjml").mailFromName + ' <' + vscode.workspace.getConfiguration("mjml").mailSender + '>', + to: recipients, + subject: subject, + html: content, + attachments: attachments + }, (err: Error, info: any) => { + if (err) { + vscode.window.showErrorMessage("Something went wrong."); + return; + } + + vscode.window.showInformationMessage("Mail has been sent successfully."); + + if (transportOptions.host && transportOptions.host == 'smtp.ethereal.email') { + let url: (string | boolean) = nodemailer.getTestMessageUrl(info); + + if (url) { + vscode.window.showInformationMessage("Preview URL: " + url); + } + } + }); + } + + private sendEmailWithMailjet(subject: string, recipients: string, content: string, attachments: any[]): void { + let recipientList: Array<{ Email: string }> = recipients.split(",").map((emailAddress: string) => { + return { Email: emailAddress }; + }); + + mailjet.connect( + vscode.workspace.getConfiguration("mjml").mailjetAPIKey, + vscode.workspace.getConfiguration("mjml").mailjetAPISecret + ).post("send").request({ + FromEmail: vscode.workspace.getConfiguration("mjml").mailSender, + FromName: vscode.workspace.getConfiguration("mjml").mailFromName, + Subject: subject, + Recipients: recipientList, + "Html-part": content, + Inline_attachments: attachments + }).then((result: object) => { + vscode.window.showInformationMessage("Mail has been sent successfully."); + }).catch((err: any) => { + vscode.window.showErrorMessage("Something went wrong."); + }); + } + + private createAttachments(content: string, mjmlPath: string, mailer?: string): any[] { + let imgPaths: string[] = []; + + let match: RegExpExecArray; + let pattern: RegExp = /]*?src=("|')([^"']+)\1/g; + while (match = pattern.exec(content)) { + imgPaths.push(match[2]); + } + + let attachments: any[] = []; + if (imgPaths) { + for (let i = 0; i < imgPaths.length; i++) { + if (imgPaths[i] && !isUrl(imgPaths[i])) { + let filePath: string = path.join(path.dirname(mjmlPath), imgPaths[i]); + + if (filePath && fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + if (mailer == 'nodemailer') { + attachments.push({ + originalPath: imgPaths[i], + filename: path.basename(filePath), + content: fs.createReadStream(filePath), + cid: Math.random().toString(36).substring(2) + i + }); + } + else { + attachments.push({ + originalPath: imgPaths[i], + "Content-type": mime.getType(filePath), + Filename: i + '_' + path.basename(filePath), + content: fs.readFileSync(filePath).toString('base64') + }); + } + } + } + } + } + + return attachments; + } + + private replaceImages(content: string, attachments: any[], mailer?: string): string { + if (attachments) { + for (let i = 0; i < attachments.length; i++) { + content = content.replace( + "src=\"" + attachments[i].originalPath + "\"", + "src=\"cid:" + ((mailer == 'nodemailer') ? attachments[i].cid : attachments[i].Filename) + "\"" + ); + } + } + + return content; + } + } diff --git a/src/export.ts b/src/export.ts index 2315425..e7f64b2 100644 --- a/src/export.ts +++ b/src/export.ts @@ -25,7 +25,15 @@ export default class ExportHTML { exportType = "." + exportType; } - vscode.window.showInputBox({ placeHolder: `File name (${defaultFileName}${exportType} or .xyz)` }).then((fileName: string) => { + vscode.window.showInputBox({ + prompt: "Filename", + placeHolder: `Enter a filename (${defaultFileName}${exportType} or .xyz).`, + value: defaultFileName + exportType + }).then((fileName: string) => { + if (!fileName) { + return; + } + let fileExtension: any = (/[.]/.exec(fileName)) ? /[^.]+$/.exec(fileName) : undefined; if (!fileExtension) { fileName += exportType; diff --git a/src/screenshot.ts b/src/screenshot.ts index 8d6763e..fb7b054 100644 --- a/src/screenshot.ts +++ b/src/screenshot.ts @@ -53,7 +53,15 @@ export default class Screenshot { screenshotType = vscode.workspace.getConfiguration("mjml").screenshotType; } - vscode.window.showInputBox({ placeHolder: `File name (${defaultFileName}.${screenshotType})` }).then((fileName: string) => { + vscode.window.showInputBox({ + prompt: "Filename", + placeHolder: "Enter a filename.", + value: defaultFileName + '.' + screenshotType + }).then((fileName: string) => { + if (!fileName) { + return; + } + fileName = fileName ? fileName.replace(/\.[^\.]+$/, "") : defaultFileName; let file: string = path.resolve(vscode.window.activeTextEditor.document.uri.fsPath, `../${fileName}.${screenshotType}`); @@ -73,8 +81,16 @@ export default class Screenshot { } } else { - vscode.window.showInputBox({ placeHolder: `Width (${defaultWidth}px)` }).then((width: any) => { - width = width.replace(/[^0-9\.]+/g, ""); + vscode.window.showInputBox({ + prompt: "Width", + placeHolder: `Enter image width (${defaultWidth}px).`, + value: defaultWidth.toString() + }).then((width: any) => { + if (!width) { + return; + } + + width = parseInt(width.replace(/[^0-9\.]+/g, "")); if (!width || Number.isNaN(parseInt(width))) { width = defaultWidth; }