diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f5aa9c..7877e3f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,10 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false // set this to true to hide the "out" folder with the compiled JS files - }, - "search.exclude": { - "out": true // set this to false to include "out" folder in search results - }, - "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bc0330..eb0f54d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log All notable changes to the "mjml" extension will be documented in this file. +### [0.1.0] (2017-12-14) +* [new] `MJML: Beautify`: [#8](https://github.com/attilabuti/vscode-mjml/issues/8) Beautify MJML code. +* [#15](https://github.com/attilabuti/vscode-mjml/pull/15): fixed preview cache issue. +* Some other improvements. + ### [0.0.9] (2017-10-06) * [new] Configuration property `mjml.screenshotWidths`: Screenshot widths. * [new] `MJML: Multiple Screenshots`: [#13](https://github.com/attilabuti/vscode-mjml/issues/13) Take multiple screenshots of the rendered MJML document. diff --git a/README.md b/README.md index ef35c53..ad4568d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ MJML preview, lint, compile for Visual Studio Code. * Send email with 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. ## It looks like this @@ -40,6 +41,7 @@ The following command is available: * **MJML: Copy HTML** Copy the result HTML to clipboard. * **MJML: Send Email** Send email with Mailjet. * **MJML: Template** Fetch official templates. +* **MJML: Beautify** or **Format Document** Beautify MJML code. ## Settings @@ -61,6 +63,7 @@ The following command is available: | `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)). | ## Snippets @@ -107,6 +110,11 @@ The following command is available: ## Change Log +### [0.1.0] (2017-12-14) +* [new] `MJML: Beautify`: [#8](https://github.com/attilabuti/vscode-mjml/issues/8) Beautify MJML code. +* [#15](https://github.com/attilabuti/vscode-mjml/pull/15): fixed preview cache issue. +* Some other improvements. + ### [0.0.9] (2017-10-06) * [new] Configuration property `mjml.screenshotWidths`: Screenshot widths. * [new] `MJML: Multiple Screenshots`: [#13](https://github.com/attilabuti/vscode-mjml/issues/13) Take multiple screenshots of the rendered MJML document. diff --git a/package-lock.json b/package-lock.json index f7cc489..b41f786 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-mjml", - "version": "0.0.9", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -415,7 +415,7 @@ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", "requires": { - "ini": "1.3.4", + "ini": "1.3.5", "proto-list": "1.2.4" } }, @@ -2726,9 +2726,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "ip": { "version": "1.1.5", @@ -2887,9 +2887,9 @@ "integrity": "sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c=" }, "js-beautify": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.3.tgz", - "integrity": "sha512-mT9skIu0OWfBQPXOGJ4CgpPBgo3tj9gxi7weQdeaxxmpKIADK2g0xS0qCtQml7Ny3Ick5Cno093LKGZTzDd2UQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.5.tgz", + "integrity": "sha512-9OhfAqGOrD7hoQBLJMTA+BKuKmoEtTJXzZ7WDF/9gvjtey1koVLuZqIY6c51aPDjbNdNtIXAkiWKVhziawE9Og==", "requires": { "config-chain": "1.1.11", "editorconfig": "0.13.3", @@ -3479,7 +3479,7 @@ "html-minifier": "3.5.5", "immutable": "3.8.2", "jquery": "3.2.1", - "js-beautify": "1.7.3", + "js-beautify": "1.7.5", "juice": "4.1.2", "lodash": "4.17.4", "mjml-validator": "3.3.5", diff --git a/package.json b/package.json index a8bf43c..45b3e20 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,15 @@ "name": "vscode-mjml", "displayName": "MJML", "description": "MJML preview, lint, compile for Visual Studio Code.", - "version": "0.0.9", + "version": "0.1.0", "publisher": "attilabuti", "license": "MIT", "readme": "README.md", "icon": "images/icon.png", "author": { "name": "Attila Buti", - "url": "www.attilabuti.com" + "email": "hello@attilabuti.com", + "url": "https://attilabuti.com" }, "homepage": "https://github.com/attilabuti/vscode-mjml#readme", "repository": { @@ -26,7 +27,8 @@ "categories": [ "Other", "Linters", - "Snippets" + "Snippets", + "Formatters" ], "keywords": [ "vscode", @@ -46,6 +48,7 @@ "onCommand:mjml.multipleScreenshots", "onCommand:mjml.sendEmail", "onCommand:mjml.template", + "onCommand:mjml.beautify", "onLanguage:mjml" ], "main": "./out/extension", @@ -136,6 +139,14 @@ "type": "string", "default": "", "description": "Comma separated list of recipients email addresses." + }, + "mjml.beautify": { + "type": "object", + "default": { + "indent_size": 2, + "wrap_attributes_indent_size": 2 + }, + "description": "Beautify options." } } }, @@ -178,6 +189,11 @@ "command": "mjml.template", "title": "Template", "category": "MJML" + }, + { + "command": "mjml.beautify", + "title": "Beautify", + "category": "MJML" } ], "languages": [ @@ -226,6 +242,7 @@ "mjml": "^3.3.5", "node-fetch": "1.7.3", "node-mailjet": "^3.2.1", - "webshot": "^0.18.0" + "webshot": "^0.18.0", + "js-beautify": "^1.7.5" } } diff --git a/src/beautify.ts b/src/beautify.ts new file mode 100644 index 0000000..88b275c --- /dev/null +++ b/src/beautify.ts @@ -0,0 +1,56 @@ +"use strict"; + +import * as vscode from "vscode"; + +import * as beautifyJS from "js-beautify"; + +import helper from "./helper"; + +export default class Beautify { + + constructor(subscriptions: vscode.Disposable[]) { + subscriptions.push( + vscode.commands.registerCommand("mjml.beautify", () => { + this.beautify(); + }) + ); + } + + private beautify(): void { + if (helper.isMJMLFile(vscode.window.activeTextEditor.document)) { + vscode.window.activeTextEditor.edit((editBuilder: vscode.TextEditorEdit) => { + editBuilder.replace(this.getRange(), this.beautifyHTML()); + }); + } + else { + vscode.window.showWarningMessage("This is not a MJML document!"); + return; + } + } + + private beautifyHTML(): any { + try { + return beautifyJS.html( + vscode.window.activeTextEditor.document.getText(), + vscode.workspace.getConfiguration("mjml").beautify + ); + } catch (err) { + vscode.window.showErrorMessage(err); + return; + } + } + + private getRange(): vscode.Range { + let document: vscode.TextDocument = vscode.window.activeTextEditor.document; + + return new vscode.Range( + new vscode.Position(0, 0), + new vscode.Position(document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length) + ); + } + + public formatDocument(): vscode.TextEdit[] { + return [vscode.TextEdit.replace(this.getRange(), this.beautifyHTML())]; + } + +} diff --git a/src/copyProvider.ts b/src/copy.ts similarity index 100% rename from src/copyProvider.ts rename to src/copy.ts diff --git a/src/emailProvider.ts b/src/email.ts similarity index 100% rename from src/emailProvider.ts rename to src/email.ts diff --git a/src/exportProvider.ts b/src/export.ts similarity index 100% rename from src/exportProvider.ts rename to src/export.ts diff --git a/src/extension.ts b/src/extension.ts index 27369b3..ec80637 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,20 +6,22 @@ import * as childProcess from "child_process"; import * as phantomJS from "phantomjs-prebuilt"; -import LintingProvider from "./lintingProvider"; -import PreviewManager from "./previewProvider"; -import ExportHTML from "./exportProvider"; -import CopyHTML from "./copyProvider"; -import Screenshot from "./screenshotProvider"; -import SendEmail from "./emailProvider"; -import Template from "./templateProvider"; +import Beautify from "./beautify"; +import CopyHTML from "./copy"; +import SendEmail from "./email"; +import ExportHTML from "./export"; +import LintingProvider from "./linter"; +import PreviewManager from "./preview"; +import Screenshot from "./screenshot"; +import Template from "./template"; +let beautify: Beautify; +let copyHTML: CopyHTML; +let sendEmail: SendEmail; +let exportHTML: ExportHTML; let linter: LintingProvider; let previewManager: PreviewManager; -let exportHTML: ExportHTML; -let copyHTML: CopyHTML; let screenshot: Screenshot; -let sendEmail: SendEmail; let template: Template; export function activate(context: vscode.ExtensionContext) { @@ -50,7 +52,7 @@ export function activate(context: vscode.ExtensionContext) { screenshot = new Screenshot(context, process.platform, phantomJS.platform, phantomJSBuilt); }); } - catch (e) { + catch (err) { vscode.window.showErrorMessage("MJML couldn't build the propper version of PhantomJS. Restart VSCode in order to try it again."); phantomJSBuilt = false; } @@ -63,10 +65,17 @@ export function activate(context: vscode.ExtensionContext) { linter = new LintingProvider(context.subscriptions); } - previewManager = new PreviewManager(context); - exportHTML = new ExportHTML(context.subscriptions); + beautify = new Beautify(context.subscriptions); + vscode.languages.registerDocumentFormattingEditProvider('mjml', { + provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] { + return beautify.formatDocument(); + } + }); + copyHTML = new CopyHTML(context.subscriptions); sendEmail = new SendEmail(context.subscriptions); + exportHTML = new ExportHTML(context.subscriptions); + previewManager = new PreviewManager(context); template = new Template(context.subscriptions); } diff --git a/src/helper.ts b/src/helper.ts index 450b671..2c4f488 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -50,7 +50,7 @@ export default class Helper { return html.html; } } - catch (e) { + catch (err) { return; } } diff --git a/src/lintingProvider.ts b/src/linter.ts similarity index 98% rename from src/lintingProvider.ts rename to src/linter.ts index 5759b61..e8a8dc4 100644 --- a/src/lintingProvider.ts +++ b/src/linter.ts @@ -55,7 +55,7 @@ export default class MJMLLintingProvider { try { MJMLDocument = documentParser(vscode.window.activeTextEditor.document.getText()); - } catch (e) { + } catch (err) { return; } @@ -87,7 +87,7 @@ export default class MJMLLintingProvider { this.diagnosticCollection.set(textDocument.uri, diagnostics); } - catch (e) { + catch (err) { this.diagnosticCollection.set(textDocument.uri, diagnostics); } } diff --git a/src/previewProvider.ts b/src/preview.ts similarity index 73% rename from src/previewProvider.ts rename to src/preview.ts index 941a2b9..3d81ad9 100644 --- a/src/previewProvider.ts +++ b/src/preview.ts @@ -16,7 +16,11 @@ export default class PreviewManager { this.subscriptions.push( vscode.commands.registerCommand("mjml.previewToSide", () => { this.previewCommand(); - }) + }), + + vscode.workspace.onDidCloseTextDocument((document: vscode.TextDocument) => { + this.removePreview(document); + }), ); } @@ -25,7 +29,7 @@ export default class PreviewManager { let mjmlPreview: MJMLView; if (!this.IDMap.hasUri(documentURI)) { - mjmlPreview = new MJMLView(this.subscriptions, vscode.window.activeTextEditor.document); + mjmlPreview = new MJMLView(vscode.window.activeTextEditor.document); this.fileMap.set(this.IDMap.add(documentURI, mjmlPreview.uri), mjmlPreview); } else { @@ -35,13 +39,37 @@ export default class PreviewManager { mjmlPreview.execute(); } - public dispose(): void { + private removePreview(document: vscode.TextDocument): void { + if (/mjml-preview/.test(document.fileName) && /sidebyside/.test(document.fileName)) { + this.dispose(); + this.fileMap.clear(); + this.IDMap.clear(); + } + else { + let documentURI: string = this.IDMap.createDocumentUri(document.uri); + + if (this.IDMap.hasUri(documentURI)) { + let mjmlPreview: MJMLView = this.fileMap.get(this.IDMap.getByUri(documentURI)); + + let id: string = this.IDMap.delete(documentURI, mjmlPreview.uri); + this.dispose(id); + this.fileMap.delete(id); + } + } + } + + public dispose(id?: string): void { let values: IterableIterator = this.fileMap.values(); let value: IteratorResult = values.next(); - while (!value.done) { - value.value.dispose(); - value = values.next(); + if (id && this.fileMap.has(id)) { + this.fileMap.get(id).dispose(); + } + else { + while (!value.done) { + value.value.dispose(); + value = values.next(); + } } } @@ -49,14 +77,14 @@ export default class PreviewManager { class MJMLView { - private registrations: vscode.Disposable[] = []; + private subscriptions: vscode.Disposable[] = []; private document: vscode.TextDocument; private provider: PreviewContentProvider; private previewUri: vscode.Uri; private viewColumn: vscode.ViewColumn; private label: string; - constructor(subscriptions: vscode.Disposable[], document: vscode.TextDocument) { + constructor(document: vscode.TextDocument) { this.document = document; this.provider = new PreviewContentProvider(this.document); @@ -65,12 +93,13 @@ class MJMLView { this.label = "MJML Preview"; - this.registrations.push(vscode.workspace.registerTextDocumentContentProvider("mjml-preview", this.provider)); - this.registerEvents(subscriptions); + this.registerEvents(); } - private registerEvents(subscriptions: vscode.Disposable[]): void { - subscriptions.push( + private registerEvents(): void { + this.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider("mjml-preview", this.provider), + vscode.workspace.onDidSaveTextDocument((document: vscode.TextDocument) => { if (helper.isMJMLFile(document)) { this.provider.update(this.previewUri); @@ -96,8 +125,8 @@ class MJMLView { } public dispose(): void { - for (let i in this.registrations) { - this.registrations[i].dispose(); + for (let i = 0; i < this.subscriptions.length; i++) { + this.subscriptions[i].dispose(); } } @@ -173,6 +202,10 @@ class IDMap { private map: Map<[string, vscode.Uri], string> = new Map<[string, vscode.Uri], string>(); + public clear(): void { + this.map.clear(); + } + private UUIDv4(): string { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c: string) => { let r: number = Math.random() * 16 | 0, v: number = c == "x" ? r : (r & 0x3 | 0x8); @@ -184,13 +217,18 @@ class IDMap { return JSON.stringify({ uri: uri }); } - public getByUri(uri: string): string | undefined { + public getByUri(uri: string, remove?: boolean): any { let keys: IterableIterator<[string, vscode.Uri]> = this.map.keys(); let key: IteratorResult<[string, vscode.Uri]> = keys.next(); while (!key.done) { if (key.value.indexOf(uri) > -1) { - return this.map.get(key.value); + if (remove) { + return key.value; + } + else { + return this.map.get(key.value); + } } key = keys.next(); @@ -210,4 +248,11 @@ class IDMap { return id; } + public delete(uri: string, previewUri: vscode.Uri): string { + let id: string = this.getByUri(uri); + this.map.delete(this.getByUri(uri, true)); + + return id; + } + } diff --git a/src/screenshotProvider.ts b/src/screenshot.ts similarity index 100% rename from src/screenshotProvider.ts rename to src/screenshot.ts diff --git a/src/templateProvider.ts b/src/template.ts similarity index 100% rename from src/templateProvider.ts rename to src/template.ts