From 1d7cd7b2c15371f22cf0257453934eb46afb1902 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 11 Jul 2019 16:53:07 -0700 Subject: [PATCH 01/37] (WIP) Initial template preview functionality --- package-lock.json | 509 ++++++++++++++++++++++++++++++- package.json | 5 + preview/assets/preview.js | 1 + preview/assets/reset.css | 349 +++++++++++++++++++++ preview/assets/styles.css | 101 ++++++ preview/index.html | 32 ++ preview/template.html | 70 +++++ src/commands/templates/prevew.ts | 213 +++++++++++++ src/types/Template.ts | 5 + 9 files changed, 1281 insertions(+), 4 deletions(-) create mode 100644 preview/assets/preview.js create mode 100644 preview/assets/reset.css create mode 100644 preview/assets/styles.css create mode 100644 preview/index.html create mode 100644 preview/template.html create mode 100644 src/commands/templates/prevew.ts diff --git a/package-lock.json b/package-lock.json index 7f04c1e..1b5151e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,12 +32,47 @@ } } }, + "@types/bluebird": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.27.tgz", + "integrity": "sha512-6BmYWSBea18+tSjjSC3QIyV93ZKAeNWGM7R6aYt1ryTZXrlHF+QLV0G2yV0viEGVyRkyQsWfMoJ0k/YghBX5sQ==", + "dev": true + }, + "@types/body-parser": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", + "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/chai": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", "dev": true }, + "@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/consolidate": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/consolidate/-/consolidate-0.14.0.tgz", + "integrity": "sha512-az7GpbuJoBJC/rGb/m7ZIsIvNY6NdUlklydmZ3RO+rPqgclj/dck3KEDesr44oZ7hk8Afs3tPJOKECVocVKcQQ==", + "dev": true, + "requires": { + "@types/bluebird": "*", + "@types/node": "*" + } + }, "@types/execa": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@types/execa/-/execa-0.9.0.tgz", @@ -47,6 +82,27 @@ "@types/node": "*" } }, + "@types/express": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.0.tgz", + "integrity": "sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.16.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.7.tgz", + "integrity": "sha512-847KvL8Q1y3TtFLRTXcVakErLJQgdpFSaq+k043xefz9raEf0C7HalpSY7OW5PyjCnY8P7bPW5t/Co9qqp+USg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, "@types/fs-extra": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.5.tgz", @@ -72,6 +128,12 @@ "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==", "dev": true }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true + }, "@types/mocha": { "version": "5.2.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", @@ -126,6 +188,22 @@ } } }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, "@types/table": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/table/-/table-4.0.5.tgz", @@ -202,6 +280,30 @@ } } }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } + } + }, "acorn": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", @@ -258,6 +360,11 @@ "sprintf-js": "~1.0.2" } }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -317,6 +424,48 @@ "tweetnacl": "^0.14.3" } }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -339,6 +488,11 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -496,6 +650,37 @@ "typedarray": "^0.0.6" } }, + "consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "requires": { + "bluebird": "^3.1.1" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -563,6 +748,16 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -592,11 +787,26 @@ "safer-buffer": "^2.1.0" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz", + "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==" + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -605,6 +815,11 @@ "once": "^1.4.0" } }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -740,6 +955,11 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, "exec-sh": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", @@ -763,6 +983,63 @@ "strip-eof": "^1.0.0" } }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -821,6 +1098,35 @@ "flat-cache": "^2.0.1" } }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -861,6 +1167,16 @@ "mime-types": "^2.1.12" } }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, "fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -981,6 +1297,18 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -1034,8 +1362,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -1068,6 +1395,11 @@ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -1241,6 +1573,11 @@ "p-defer": "^1.0.0" } }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, "mem": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", @@ -1264,6 +1601,21 @@ "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", "dev": true }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, "mime-db": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", @@ -1376,8 +1728,7 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "mute-stream": { "version": "0.0.7", @@ -1501,6 +1852,11 @@ } } }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -1525,6 +1881,14 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1639,6 +2003,11 @@ "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=" }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -1661,6 +2030,11 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", @@ -1731,6 +2105,15 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -1761,6 +2144,22 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -1886,11 +2285,64 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -1967,6 +2419,11 @@ "tweetnacl": "~0.14.0" } }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -2069,6 +2526,11 @@ "os-tmpdir": "~1.0.2" } }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -2145,6 +2607,30 @@ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -2162,6 +2648,11 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, "untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", @@ -2181,11 +2672,21 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/package.json b/package.json index 2d80684..f8bee69 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "dependencies": { "@types/traverse": "^0.6.32", "chalk": "^2.4.2", + "consolidate": "^0.15.1", "directory-tree": "^2.2.3", + "ejs": "^2.6.2", + "express": "^4.17.1", "fs-extra": "^7.0.1", "inquirer": "^6.2.1", "lodash": "^4.17.11", @@ -21,7 +24,9 @@ }, "devDependencies": { "@types/chai": "^4.1.4", + "@types/consolidate": "^0.14.0", "@types/execa": "^0.9.0", + "@types/express": "^4.17.0", "@types/fs-extra": "^5.0.5", "@types/inquirer": "^6.0.0", "@types/lodash": "^4.14.123", diff --git a/preview/assets/preview.js b/preview/assets/preview.js new file mode 100644 index 0000000..4c1fc8f --- /dev/null +++ b/preview/assets/preview.js @@ -0,0 +1 @@ +console.log(templates) diff --git a/preview/assets/reset.css b/preview/assets/reset.css new file mode 100644 index 0000000..b6eb821 --- /dev/null +++ b/preview/assets/reset.css @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + + html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} \ No newline at end of file diff --git a/preview/assets/styles.css b/preview/assets/styles.css new file mode 100644 index 0000000..28669ff --- /dev/null +++ b/preview/assets/styles.css @@ -0,0 +1,101 @@ +@import url("reset.css"); + +body { + background-color: #FFF; + font-family: 'Roboto', Helvetica, Arial, sans-serif; + font-size: 1rem; + font-weight: bold; +} + + +.f-code { + font-family: 'Roboto Mono', monospace; +} + +.toolbar { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #FFF; + height: 50px; + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); + box-sizing: border-box; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 2; + box-sizing: border-box; + padding: 15px; +} +.toolbar_title { + font-size: 1rem; + font-weight: 500; + color: #444; + align-self: center; + display: flex; + justify-content: space-between; + align-items: baseline; +} +.toolbar_alias { + display: inline-block; + font-size: 10px; + font-weight: bold; + color: #FFF; + margin-left: 5px; + border-radius: 3px; + background-color: #665dff; + padding: 4px 6px; +} +.toolbar_back { + display: inline-block; + text-decoration: none; + border: 2px solid #dbdaeb; + padding: 4px 6px 6px; + border-radius: 3px; + color: #929395; +} + +.preview-iframe { + display: block; + position: absolute; + bottom: 0; + height: calc(100vh - 50px); + width: 100%; + z-index: 1; +} + + +.toggle { + display: flex; + justify-content: space-between; + border: 2px solid #dbdaeb; + border-radius: 4px; +} +.toggle_item { + box-sizing: border-box; + display: inline-block; + text-decoration: none; + color: #929395; + padding: 6px 12px; + font-size: 13px; + margin: 3px 3px 3px; + font-weight: 500; +} +.toggle_item:last-child { + margin-left: 0; +} +.toggle_item:hover { + color: #444; +} +.toggle_item--active, +.toggle_item--active:hover { + background-color: #665dff; + color: #FFF; + border-radius: 2px; +} + + +.hidden { + display: none; +} \ No newline at end of file diff --git a/preview/index.html b/preview/index.html new file mode 100644 index 0000000..580bcb6 --- /dev/null +++ b/preview/index.html @@ -0,0 +1,32 @@ + + + + Postmark template preview + + + + + + + +

Templates

+ + +

Layouts

+ + + diff --git a/preview/template.html b/preview/template.html new file mode 100644 index 0000000..6e4d900 --- /dev/null +++ b/preview/template.html @@ -0,0 +1,70 @@ + + + + + + Postmark template preview + + + + + + + +
+ +

+ <%- template.Name %> + <%- template.Alias %> +

+
+ HTML + Text +
+
+ + + + + + + + diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/prevew.ts new file mode 100644 index 0000000..e5450b9 --- /dev/null +++ b/src/commands/templates/prevew.ts @@ -0,0 +1,213 @@ +import { join, dirname } from 'path' +import { readJsonSync, readFileSync, existsSync } from 'fs-extra' +import traverse from 'traverse' +import { filter, find, replace } from 'lodash' +import untildify from 'untildify' +import dirTree from 'directory-tree' +import express from 'express' +import consolidate from 'consolidate' +import { + TemplatePreviewArguments, + TemplateManifest, + MetaFileTraverse, + MetaFile, +} from '../../types' +import { log } from '../../utils' + +export const command = 'preview [options]' +export const desc = 'Preview your templates and layouts together' +export const builder = { + port: { + type: 'number', + describe: 'The port to open up the preview server on', + default: '3005', + alias: 'p', + }, +} +export const handler = (args: TemplatePreviewArguments) => exec(args) + +/** + * Execute the command + */ +const exec = (args: TemplatePreviewArguments): void => validateDirectory(args) + +// TODO: we can probably reuse this with push.ts +const validateDirectory = (args: TemplatePreviewArguments) => { + const rootPath: string = untildify(args.templatesdirectory) + + // Check if path exists + if (!existsSync(rootPath)) { + log('The provided path does not exist', { error: true }) + return process.exit(1) + } + + return preview(args) +} + +/** + * Preview + */ +const preview = (args: TemplatePreviewArguments) => { + const app = express() + const port = args.port + + app.use(express.static('preview/assets')) + + app.get('/', (req, res) => { + const manifest = createManifest(args.templatesdirectory) + const templates = compileTemplates(manifest) + + consolidate.ejs( + 'preview/index.html', + { + templates: filter(templates, { TemplateType: 'Standard' }), + layouts: filter(templates, { TemplateType: 'Layout' }), + }, + (err, html) => { + if (err) res.send(err) + + return res.send(html) + } + ) + }) + + app.get('/:alias', (req, res) => { + const manifest = createManifest(args.templatesdirectory) + const compiled = compileTemplates(manifest) + const template: any = find(compiled, { Alias: req.params.alias }) + + consolidate.ejs( + 'preview/template.html', + { template: template }, + (err, html) => { + if (err) res.send(err) + + return res.send(html) + } + ) + }) + + app.get('/html/:alias', (req, res) => { + const manifest = createManifest(args.templatesdirectory) + const compiled = compileTemplates(manifest) + const template: any = find(compiled, { Alias: req.params.alias }) + + if (template && template.HtmlBody) { + return res.send(template.HtmlBody) + } + + return res.status(404) + }) + + app.get('/text/:alias', (req, res) => { + const manifest = createManifest(args.templatesdirectory) + const compiled = compileTemplates(manifest) + const template: any = find(compiled, { Alias: req.params.alias }) + + if (template && template.TextBody) { + res.set('Content-Type', 'text/plain') + res.write(template.TextBody) + return res.end() + } + + return res.status(404) + }) + + app.listen(port, () => + console.log(`Previews available at http://localhost:${port}!`) + ) +} + +const compileTemplates = (manifest: TemplateManifest[]) => { + let compiled: any = [] + const layouts = filter(manifest, { TemplateType: 'Layout' }) + const templates = filter(manifest, { TemplateType: 'Standard' }) + + templates.forEach(template => { + const layout = find(layouts, { Alias: template.LayoutTemplate || '' }) + + if (!layout) return + if (!layout.HtmlBody && !layout.TextBody) return + + compiled.push({ + ...template, + HtmlBody: compileTemplate(layout.HtmlBody || '', template.HtmlBody || ''), + TextBody: compileTemplate(layout.TextBody || '', template.TextBody || ''), + }) + }) + + layouts.forEach(layout => { + if (!layout.HtmlBody && !layout.TextBody) return + const content = 'Template content is inserted here.' + + compiled.push({ + ...layout, + HtmlBody: compileTemplate(layout.HtmlBody || '', content), + TextBody: compileTemplate(layout.TextBody || '', content), + }) + }) + + return compiled +} + +const compileTemplate = (layout: string, template: string): string => + replace(layout, /({{{)(.?@content.?)(}}})/g, template) + +/** + * Parses templates folder and files + */ +const createManifest = (path: string): TemplateManifest[] => { + let manifest: TemplateManifest[] = [] + + // Return empty array if path does not exist + if (!existsSync(path)) return manifest + + // Find meta files and flatten into collection + const list: MetaFileTraverse[] = FindMetaFiles(path) + + // Parse each directory + list.forEach(file => { + const item = createManifestItem(file) + if (item) manifest.push(item) + }) + + return manifest +} + +/** + * Gathers the template's content and metadata based on the metadata file location + */ +const createManifestItem = (file: any): MetaFile | null => { + const { path } = file // Path to meta file + const rootPath = dirname(path) // Folder path + const htmlPath = join(rootPath, 'content.html') // HTML path + const textPath = join(rootPath, 'content.txt') // Text path + + // Check if meta file exists + if (existsSync(path)) { + const metaFile: MetaFile = readJsonSync(path) + const htmlFile: string = existsSync(htmlPath) + ? readFileSync(htmlPath, 'utf-8') + : '' + const textFile: string = existsSync(textPath) + ? readFileSync(textPath, 'utf-8') + : '' + + return { + HtmlBody: htmlFile, + TextBody: textFile, + ...metaFile, + } + } + + return null +} + +/** + * Searches for all metadata files and flattens into a collection + */ +const FindMetaFiles = (path: string): MetaFileTraverse[] => + traverse(dirTree(path)).reduce((acc, file) => { + if (file.name === 'meta.json') acc.push(file) + return acc + }, []) diff --git a/src/types/Template.ts b/src/types/Template.ts index 579bce4..c63dc7f 100644 --- a/src/types/Template.ts +++ b/src/types/Template.ts @@ -66,6 +66,11 @@ export interface TemplatePushArguments { force: boolean } +export interface TemplatePreviewArguments { + templatesdirectory: string + port: number +} + export interface MetaFile { Name: string Alias: string From 57b6c33e159ce5d9d2ad52efeaff6663b96ae708 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Fri, 12 Jul 2019 14:01:53 -0700 Subject: [PATCH 02/37] Tweak preview UI --- preview/assets/favicon.ico | Bin 0 -> 6518 bytes preview/assets/folder.svg | 1 + preview/assets/icon.png | Bin 0 -> 997 bytes preview/assets/preview.js | 27 +++++- preview/assets/styles.css | 145 ++++++++++++++++++++++++++++--- preview/index.html | 73 ++++++++++------ preview/partials/head.html | 8 ++ preview/template.html | 89 ++++++------------- src/commands/templates/prevew.ts | 1 + 9 files changed, 245 insertions(+), 99 deletions(-) create mode 100644 preview/assets/favicon.ico create mode 100644 preview/assets/folder.svg create mode 100644 preview/assets/icon.png create mode 100644 preview/partials/head.html diff --git a/preview/assets/favicon.ico b/preview/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e381ad8ce9869e60708e3616a50d6dd684805e4e GIT binary patch literal 6518 zcmeHK-AhzK6dwh(mqI8oDP$>%rKpFNpeRs!s;KD0AOxc)CF}#t^2w+kd<*;=B6{n) zP^_myEC|Y2Dl^^6k4yx2)!lqMo!{)9+w66(&E0P9gAN?eo!L3_oAWz!W{ye~sbW=L zu2_duxJ0SVN~usN(_f?15grTk7*F9D>2X#{@%X&i>?_Xx;FJkWZbR2?8~lpkP2;b#BC>iFMHK9OciG@r|s|d zdiv*OkM%>>(|ZscW5zFS-#$NmgpKhds1BtA?SlM^JC)IIB`?9U6#kc*+uk6cr zqN-zcRJ=7}WsmsPw5R>Q)IUyVx_haFZOfW(SlO$#?D%O8_8lU`MPdiNkpJd9ukS$Y zkw@>Qv-ahMXO6>0=k22p)tpDKWiQqmHn3gd4fkY-rG^Ffscw9Sdb9|SL!>QcKQcEr zM-T?rxP|D)<8cy6Bog#LMAnBk`~~L`i~%#1O3}!4l0L_00~M@^wSfVw4A;O$)A;zk zv2To2*B4UVL%|BKiM4^@!q@S~zD8$K8r!@Ng8YVGzyOxPzk@%RNV3m^zV9sCJz{NO zxbQXq7+Xwy#2mnx7O}zK!0(;B+oJ0yvfu87xG?=^T*0^Ie|2}%sa@p#uV}hr9WN6< zpZKd6F;b1KRJyBL@1vxyfz-`g|B8w023*Ko#xZ|2`x@f-i&-h1^|e1m^$eAPAo{c&<{C+~^h z1}|*4{|3JA_)9$fY7hPkjc?7rUw_^6P5%x49sB~-pPGNv#7_O)Ily&rz`|>K)ZY$1 zYC!B^GWmfolderCreated with Sketch. \ No newline at end of file diff --git a/preview/assets/icon.png b/preview/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b0f34f87b08863c8c97230dab3454a85ae810357 GIT binary patch literal 997 zcmZvbYg7{i7=~XU0ht4FaA1=$N)MvhI3~A|ZR`RAL_pjQjhl)%hX}H`D8fJ_AX3=76b?KiDnct z07O4uUWk4-{X3>cdYX}RdKyrJ0|efBu{Mt7VPwR?WF4$5@p=rtOt9Q==m0E;SZ{*% zI(+y(UJQXq0NENZA0hED766q6>p60$-EVdVm9e3YuTRZwRay z7EoGmR0qgdOsBFPw|mYKB#|-E#~w}@hLwHSZyez@7gAT6F6k(Xy_;{W9B)dOD85YE zSf~p((Re+Ya-a}ICYsCsAAQ~{>R`LLMew%hW@d_OIKgf9jEQYeb;AN;nU1-3N22u* zIRDkCT1jEi69zVgg?I+=I<^_V!eiLl=FX7bkPumb{N^(;|;r;JV(FmzoL=m@8z#uJg4dbz7snDobfp{LS}_nbi5( z3AX!jT4dgnZl=mu$cneG%%Ri&)?85+Ug6VxYF47{UlUZRgPtnu>;#8iO}2w#w7IAc zD&J1GGb!ZhaLa+24Dxi`sSj#Hf4G{+K78xE?n1A0f4frHG<{^&)Mon9HgD$%Q`zZ} zrNFIHQFG_+piH0f4b~f|-Eqta5z8@1-hMJlYN54FvY_^Vku)=P-6P*z+{0LE-6bV1 ztZo03q8cX+sR@ruqG{TwYAdN3l{){NXUAE|8MhJbCws3H={R$nFWqgpc0%!da5a9r zJ3|&y5i~(Nexxt=Exs>Hx9H>WG54rKQ#Bd!N>enk-bKSB?fVa`1YwWy@~Y*V`~S}y awRF(=*@GRT?6|M>eTAQQAg_UQ=-fYulc0A1 literal 0 HcmV?d00001 diff --git a/preview/assets/preview.js b/preview/assets/preview.js index 4c1fc8f..b97f5fe 100644 --- a/preview/assets/preview.js +++ b/preview/assets/preview.js @@ -1 +1,26 @@ -console.log(templates) +var html = document.querySelector('.js-html') +var toggles = document.querySelectorAll('.js-toggle-mode') +var activeToggleClass = 'toggle_item--active' + +toggles.forEach(function(toggle) { + toggle.addEventListener('click', function(event) { + event.preventDefault() + // Reset state + resetMode() + + // Add active class + this.classList.add(activeToggleClass) + + // Show preview + document + .querySelector('.js-' + this.dataset.mode) + .classList.remove('hidden') + }) +}) + +function resetMode() { + toggles.forEach(function(toggle) { + toggle.classList.remove(activeToggleClass) + document.querySelector('.js-' + toggle.dataset.mode).classList.add('hidden') + }) +} diff --git a/preview/assets/styles.css b/preview/assets/styles.css index 28669ff..1f96f27 100644 --- a/preview/assets/styles.css +++ b/preview/assets/styles.css @@ -1,12 +1,41 @@ @import url("reset.css"); +:root { + --c-primary: #ff7c83; + --c-bg: #eeeff6; + --c-text-primary: #222; + --c-text-secondary: #8e91a6; + --c-postmark: #FFDE00; +} + body { background-color: #FFF; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 1rem; - font-weight: bold; + font-weight: 500; +} + +h2 { + text-align: center; + color: var(--c-text-secondary); + font-size: 13px; + text-transform: uppercase; + font-weight: normal; + letter-spacing: 1px; + margin: 40px 0 20px; } +.icon { + display: inline-block; + background-image: url(icon.png); + background-repeat: no-repeat; + background-position: center center; + background-size: 75%; + width: 18px; + height: 18px; + border-radius: 50%; + background-color: var(--c-postmark); +} .f-code { font-family: 'Roboto Mono', monospace; @@ -18,24 +47,24 @@ body { align-items: center; background-color: #FFF; height: 50px; - box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 3px rgba(0, 0, 0, .1); box-sizing: border-box; position: fixed; top: 0; left: 0; right: 0; - z-index: 2; + z-index: 3; box-sizing: border-box; padding: 15px; } .toolbar_title { font-size: 1rem; font-weight: 500; - color: #444; + color: var(--c-text-main); align-self: center; display: flex; justify-content: space-between; - align-items: baseline; + align-items: center; } .toolbar_alias { display: inline-block; @@ -44,7 +73,7 @@ body { color: #FFF; margin-left: 5px; border-radius: 3px; - background-color: #665dff; + background-color: var(--c-primary); padding: 4px 6px; } .toolbar_back { @@ -53,17 +82,67 @@ body { border: 2px solid #dbdaeb; padding: 4px 6px 6px; border-radius: 3px; - color: #929395; + color: var(--c-text-secondary); +} +.toolbar_path { + display: flex; + align-content: center; + font-size: 13px; + color: var(--c-text-secondary); +} +.toolbar_path::before { + display: inline-block; + content: ''; + background-image: url(folder.svg); + background-repeat: no-repeat; + background-size: 100%; + width: 15px; + height: 12px; + margin-right: 5px; +} +.toolbar_icon { + margin-right: 8px; +} + + +.sub-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #FFF; + height: 40px; + box-sizing: border-box; + position: absolute; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + box-sizing: border-box; + padding: 0 15px; + box-shadow: 0 -2px 3px rgba(0, 0, 0, .1); +} +.subject { + font-size: 13px; + font-weight: normal; +} +.subject_label { + color: var(--c-text-secondary); +} +.subject_line { + color: var(--c-text-primary); } .preview-iframe { display: block; position: absolute; - bottom: 0; - height: calc(100vh - 50px); + top: 50px; + height: calc(100vh - 90px); width: 100%; z-index: 1; } +.preview-frame--layout { + height: calc(100vh - 50px); +} .toggle { @@ -76,7 +155,7 @@ body { box-sizing: border-box; display: inline-block; text-decoration: none; - color: #929395; + color: var(--c-text-secondary); padding: 6px 12px; font-size: 13px; margin: 3px 3px 3px; @@ -86,13 +165,55 @@ body { margin-left: 0; } .toggle_item:hover { - color: #444; + color: var(--c-text-primary); } .toggle_item--active, .toggle_item--active:hover { - background-color: #665dff; + background-color: var(--c-primary); color: #FFF; border-radius: 2px; + cursor: default; +} + + +.list-body { + position: fixed; + bottom: 0; + top: 50px; + left: 0; + right: 0; + background-color: var(--c-bg); + overflow-y: auto; + padding: 0 0 50px; +} + +.template-list { + list-style: none; + padding: 0; + margin: 0 auto; + width: 80%; +} +.template-link { + display: flex; + justify-content: space-between; + background-color: #fff; + padding: 10px 25px; + border: 2px solid transparent; + margin-bottom: 3px; + text-decoration: none; + color: var(--c-text-primary); + border-radius: 3px; + box-shadow: 0 0 2px rgba(0, 0, 0, .08); + transition: 100ms all ease-in-out; + align-items: center; +} +.template-link:hover { + border: 2px solid var(--c-primary); + background-color: #fff2f2; +} +.template-layout { + color: var(--c-text-secondary); + font-size: 12px; } diff --git a/preview/index.html b/preview/index.html index 580bcb6..7496db6 100644 --- a/preview/index.html +++ b/preview/index.html @@ -1,32 +1,55 @@ - - Postmark template preview - - +<%- include("partials/head.html") %> + +
+

Postmark Templates

+ <%- path %> +
- - - - -

Templates

- +

<%- templates.length %> Templates

-

Layouts

- + <% if (templates.length > 0) { %> + + <% } else { %> + meta.json +
+{
+  "Name": "Welcome",
+  "Alias": "welcome",
+  "Subject": "Your subject line",
+  "TemplateType": "Standard",
+  "LayoutTemplate": "Enter the layout alias"
+}
+      
+ <% } %> + diff --git a/preview/partials/head.html b/preview/partials/head.html new file mode 100644 index 0000000..3dce219 --- /dev/null +++ b/preview/partials/head.html @@ -0,0 +1,8 @@ + + + + Postmark template preview + + + + \ No newline at end of file diff --git a/preview/template.html b/preview/template.html index 6e4d900..2f47269 100644 --- a/preview/template.html +++ b/preview/template.html @@ -1,70 +1,37 @@ - - - - Postmark template preview - - - - - - +<%- include("partials/head.html") %> -
- -

- <%- template.Name %> - <%- template.Alias %> -

-
- HTML - Text +
+
+ +
+
+

+ <%- template.Name %> + <%- template.Alias %> +

+
+
+
+ HTML + Text +
- - - - - - + + diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/prevew.ts index e5450b9..b913c11 100644 --- a/src/commands/templates/prevew.ts +++ b/src/commands/templates/prevew.ts @@ -62,6 +62,7 @@ const preview = (args: TemplatePreviewArguments) => { { templates: filter(templates, { TemplateType: 'Standard' }), layouts: filter(templates, { TemplateType: 'Layout' }), + path: untildify(args.templatesdirectory), }, (err, html) => { if (err) res.send(err) From a97ca321900578142833b5fa35a9141396b91212 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 14 Nov 2019 11:38:23 -0700 Subject: [PATCH 03/37] Ignore preview directory --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 5321ef3..fc37f89 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,5 @@ "esModuleInterop": true }, "include": ["src/**/*"], - "exclude": ["test/**/*"] + "exclude": ["test/**/*", "preview/**/*"] } From 0194f0a70eb4044eaede9cee07d7823b674af96b Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 14 Nov 2019 11:39:08 -0700 Subject: [PATCH 04/37] Use integer instead of string for port --- src/commands/templates/prevew.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/prevew.ts index b913c11..749583f 100644 --- a/src/commands/templates/prevew.ts +++ b/src/commands/templates/prevew.ts @@ -20,7 +20,7 @@ export const builder = { port: { type: 'number', describe: 'The port to open up the preview server on', - default: '3005', + default: 3005, alias: 'p', }, } From b0591c4b8ac8907321933cd7c2ca86e714e5de01 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 14 Nov 2019 15:28:04 -0700 Subject: [PATCH 05/37] Use ejs file extension --- preview/{index.html => index.ejs} | 2 +- preview/partials/{head.html => head.ejs} | 0 preview/{template.html => template.ejs} | 4 ++-- src/commands/templates/prevew.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) rename preview/{index.html => index.ejs} (97%) rename preview/partials/{head.html => head.ejs} (100%) rename preview/{template.html => template.ejs} (94%) diff --git a/preview/index.html b/preview/index.ejs similarity index 97% rename from preview/index.html rename to preview/index.ejs index 7496db6..3b2c85b 100644 --- a/preview/index.html +++ b/preview/index.ejs @@ -1,6 +1,6 @@ -<%- include("partials/head.html") %> +<%- include("partials/head.ejs") %>

Postmark Templates

diff --git a/preview/partials/head.html b/preview/partials/head.ejs similarity index 100% rename from preview/partials/head.html rename to preview/partials/head.ejs diff --git a/preview/template.html b/preview/template.ejs similarity index 94% rename from preview/template.html rename to preview/template.ejs index 2f47269..27e0618 100644 --- a/preview/template.html +++ b/preview/template.ejs @@ -1,6 +1,6 @@ -<%- include("partials/head.html") %> +<%- include("partials/head.ejs") %>
@@ -22,7 +22,7 @@

<% if (template.TemplateType === 'Standard') {%>
- Subject: + Subject <%- template.Subject || 'No subject' %>
diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/prevew.ts index 749583f..8f0763a 100644 --- a/src/commands/templates/prevew.ts +++ b/src/commands/templates/prevew.ts @@ -58,7 +58,7 @@ const preview = (args: TemplatePreviewArguments) => { const templates = compileTemplates(manifest) consolidate.ejs( - 'preview/index.html', + 'preview/index.ejs', { templates: filter(templates, { TemplateType: 'Standard' }), layouts: filter(templates, { TemplateType: 'Layout' }), @@ -78,7 +78,7 @@ const preview = (args: TemplatePreviewArguments) => { const template: any = find(compiled, { Alias: req.params.alias }) consolidate.ejs( - 'preview/template.html', + 'preview/template.ejs', { template: template }, (err, html) => { if (err) res.send(err) From 33480bbafb86a9255e922f1533daea5635dbb783 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 20 Nov 2019 09:27:48 -0700 Subject: [PATCH 06/37] UI improvements --- preview/assets/{ => images}/favicon.ico | Bin preview/assets/{ => images}/folder.svg | 0 preview/assets/{ => images}/icon.png | Bin preview/assets/images/templates.svg | 32 +++ preview/assets/{ => js}/preview.js | 0 preview/assets/styles.css | 222 ---------------- preview/assets/{ => styles}/reset.css | 0 preview/assets/styles/styles.css | 337 ++++++++++++++++++++++++ preview/index.ejs | 89 ++++--- preview/partials/head.ejs | 2 +- preview/template.ejs | 36 +-- src/commands/templates/prevew.ts | 54 ++-- 12 files changed, 483 insertions(+), 289 deletions(-) rename preview/assets/{ => images}/favicon.ico (100%) rename preview/assets/{ => images}/folder.svg (100%) rename preview/assets/{ => images}/icon.png (100%) create mode 100644 preview/assets/images/templates.svg rename preview/assets/{ => js}/preview.js (100%) delete mode 100644 preview/assets/styles.css rename preview/assets/{ => styles}/reset.css (100%) create mode 100644 preview/assets/styles/styles.css diff --git a/preview/assets/favicon.ico b/preview/assets/images/favicon.ico similarity index 100% rename from preview/assets/favicon.ico rename to preview/assets/images/favicon.ico diff --git a/preview/assets/folder.svg b/preview/assets/images/folder.svg similarity index 100% rename from preview/assets/folder.svg rename to preview/assets/images/folder.svg diff --git a/preview/assets/icon.png b/preview/assets/images/icon.png similarity index 100% rename from preview/assets/icon.png rename to preview/assets/images/icon.png diff --git a/preview/assets/images/templates.svg b/preview/assets/images/templates.svg new file mode 100644 index 0000000..d512161 --- /dev/null +++ b/preview/assets/images/templates.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/preview/assets/preview.js b/preview/assets/js/preview.js similarity index 100% rename from preview/assets/preview.js rename to preview/assets/js/preview.js diff --git a/preview/assets/styles.css b/preview/assets/styles.css deleted file mode 100644 index 1f96f27..0000000 --- a/preview/assets/styles.css +++ /dev/null @@ -1,222 +0,0 @@ -@import url("reset.css"); - -:root { - --c-primary: #ff7c83; - --c-bg: #eeeff6; - --c-text-primary: #222; - --c-text-secondary: #8e91a6; - --c-postmark: #FFDE00; -} - -body { - background-color: #FFF; - font-family: 'Roboto', Helvetica, Arial, sans-serif; - font-size: 1rem; - font-weight: 500; -} - -h2 { - text-align: center; - color: var(--c-text-secondary); - font-size: 13px; - text-transform: uppercase; - font-weight: normal; - letter-spacing: 1px; - margin: 40px 0 20px; -} - -.icon { - display: inline-block; - background-image: url(icon.png); - background-repeat: no-repeat; - background-position: center center; - background-size: 75%; - width: 18px; - height: 18px; - border-radius: 50%; - background-color: var(--c-postmark); -} - -.f-code { - font-family: 'Roboto Mono', monospace; -} - -.toolbar { - display: flex; - justify-content: space-between; - align-items: center; - background-color: #FFF; - height: 50px; - box-shadow: 0 2px 3px rgba(0, 0, 0, .1); - box-sizing: border-box; - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 3; - box-sizing: border-box; - padding: 15px; -} -.toolbar_title { - font-size: 1rem; - font-weight: 500; - color: var(--c-text-main); - align-self: center; - display: flex; - justify-content: space-between; - align-items: center; -} -.toolbar_alias { - display: inline-block; - font-size: 10px; - font-weight: bold; - color: #FFF; - margin-left: 5px; - border-radius: 3px; - background-color: var(--c-primary); - padding: 4px 6px; -} -.toolbar_back { - display: inline-block; - text-decoration: none; - border: 2px solid #dbdaeb; - padding: 4px 6px 6px; - border-radius: 3px; - color: var(--c-text-secondary); -} -.toolbar_path { - display: flex; - align-content: center; - font-size: 13px; - color: var(--c-text-secondary); -} -.toolbar_path::before { - display: inline-block; - content: ''; - background-image: url(folder.svg); - background-repeat: no-repeat; - background-size: 100%; - width: 15px; - height: 12px; - margin-right: 5px; -} -.toolbar_icon { - margin-right: 8px; -} - - -.sub-toolbar { - display: flex; - justify-content: space-between; - align-items: center; - background-color: #FFF; - height: 40px; - box-sizing: border-box; - position: absolute; - bottom: 0; - left: 0; - right: 0; - z-index: 2; - box-sizing: border-box; - padding: 0 15px; - box-shadow: 0 -2px 3px rgba(0, 0, 0, .1); -} -.subject { - font-size: 13px; - font-weight: normal; -} -.subject_label { - color: var(--c-text-secondary); -} -.subject_line { - color: var(--c-text-primary); -} - -.preview-iframe { - display: block; - position: absolute; - top: 50px; - height: calc(100vh - 90px); - width: 100%; - z-index: 1; -} -.preview-frame--layout { - height: calc(100vh - 50px); -} - - -.toggle { - display: flex; - justify-content: space-between; - border: 2px solid #dbdaeb; - border-radius: 4px; -} -.toggle_item { - box-sizing: border-box; - display: inline-block; - text-decoration: none; - color: var(--c-text-secondary); - padding: 6px 12px; - font-size: 13px; - margin: 3px 3px 3px; - font-weight: 500; -} -.toggle_item:last-child { - margin-left: 0; -} -.toggle_item:hover { - color: var(--c-text-primary); -} -.toggle_item--active, -.toggle_item--active:hover { - background-color: var(--c-primary); - color: #FFF; - border-radius: 2px; - cursor: default; -} - - -.list-body { - position: fixed; - bottom: 0; - top: 50px; - left: 0; - right: 0; - background-color: var(--c-bg); - overflow-y: auto; - padding: 0 0 50px; -} - -.template-list { - list-style: none; - padding: 0; - margin: 0 auto; - width: 80%; -} -.template-link { - display: flex; - justify-content: space-between; - background-color: #fff; - padding: 10px 25px; - border: 2px solid transparent; - margin-bottom: 3px; - text-decoration: none; - color: var(--c-text-primary); - border-radius: 3px; - box-shadow: 0 0 2px rgba(0, 0, 0, .08); - transition: 100ms all ease-in-out; - align-items: center; -} -.template-link:hover { - border: 2px solid var(--c-primary); - background-color: #fff2f2; -} -.template-layout { - color: var(--c-text-secondary); - font-size: 12px; -} - - -.hidden { - display: none; -} \ No newline at end of file diff --git a/preview/assets/reset.css b/preview/assets/styles/reset.css similarity index 100% rename from preview/assets/reset.css rename to preview/assets/styles/reset.css diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css new file mode 100644 index 0000000..bbbf40b --- /dev/null +++ b/preview/assets/styles/styles.css @@ -0,0 +1,337 @@ +@import url("reset.css"); + +:root { + --c-primary: #ff7c83; + --c-bg: #eeeff6; + --c-text-primary: #3d3f4e; + --c-text-secondary: #8e91a6; + --c-postmark: #FFDE00; +} + +body { + background-color: #FFF; + font-family: 'Roboto', Helvetica, Arial, sans-serif; + font-size: 1rem; + color: var(--c-text-primary); +} +.container { + margin: 0 auto; + padding: 56px 0; + max-width: 800px; +} +p, +li { + font-size: 16px; + line-height: 1.5; +} +a, +a:visited { + color: var(--c-text-primary); +} + +.section-title { + text-align: center; + color: var(--c-text-secondary); + font-size: 13px; + text-transform: uppercase; + font-weight: normal; + letter-spacing: 1px; + margin: 40px 0 20px; +} + + +/* *************************** */ +/** Toolbar **/ +/* *************************** */ + +.toolbar { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #FFF; + height: 56px; + box-shadow: 0 0 5px rgba(0, 0, 0, .1); + box-sizing: border-box; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 3; + box-sizing: border-box; + padding: 0 15px; +} + .toolbar_meta { + display: flex; + align-items: center; + } + .toolbar_title { + display: flex; + font-size: 14px; + color: var(--c-text-main); + margin: 0; + align-items: center; + } + .toolbar_title--index { + font-size: 16px; + } + .toolbar_alias { + margin-top: 4px; + } + .toolbar_text { + margin: 0 0 0 10px; + } + .toolbar_back { + text-decoration: none; + border: 2px solid #dbdaeb; + padding: 4px 6px 6px; + border-radius: 3px; + color: var(--c-text-secondary); + transition: 100ms all ease-in-out; + } + .toolbar_back:hover { + color: var(--c-primary); + background-color: #fff2f2; + border-color: var(--c-primary); + } + .toolbar_path { + display: flex; + align-content: center; + font-size: 12px; + color: var(--c-text-secondary); + } + .toolbar_path::before { + display: inline-block; + content: ''; + background-image: url(../images/folder.svg); + background-repeat: no-repeat; + background-size: 100%; + width: 15px; + height: 12px; + margin-right: 5px; + } + .toolbar_icon { + margin-right: 5px; + } + .toolbar_layout { + margin-right: 10px; + } + + +/* *************************** */ +/* Toggle button */ +/* *************************** */ + +.toggle { + display: flex; + justify-content: space-between; + border: 2px solid #dbdaeb; + border-radius: 4px; +} + .toggle_item { + box-sizing: border-box; + display: inline-block; + text-decoration: none; + color: var(--c-text-secondary); + padding: 6px 12px; + font-size: 13px; + margin: 3px 3px 3px; + font-weight: bold; + transition: 100ms all ease-in-out; + } + .toggle_item:last-child { + margin-left: 0; + } + .toggle_item:hover { + color: var(--c-primary); + } + .toggle_item--active, + .toggle_item--active:hover { + background-color: var(--c-primary); + color: #FFF; + border-radius: 2px; + cursor: default; + } + + +.template-list { + list-style: none; + padding: 0; + margin: 0 auto; + width: 80%; +} +.template-link { + display: flex; + justify-content: space-between; + background-color: #fff; + padding: 10px 15px; + border: 2px solid transparent; + margin-bottom: 5px; + text-decoration: none; + color: var(--c-text-primary); + border-radius: 4px; + box-shadow: 0 0 2px rgba(0, 0, 0, .08); + transition: 100ms all ease-in-out; + align-items: center; +} + .template-link:hover { + border: 2px solid var(--c-primary); + background-color: #fff2f2; + color: var(--c-primary) !important; + } + +.template-title { + margin: 0; + font-size: 16px; + line-height: 1.4; +} +.template-layout { + color: var(--c-text-secondary); + font-size: 12px; +} + + +/* *************************** */ +/* Misc */ +/* *************************** */ + +.sub-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #FFF; + height: 40px; + box-sizing: border-box; + position: absolute; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + box-sizing: border-box; + padding: 0 15px; + box-shadow: 0 0 5px rgba(0, 0, 0, .1); +} +.subject { + font-size: 13px; + font-weight: normal; +} +.subject_label { + color: var(--c-text-secondary); + margin-right: 8px; +} +.subject_line { + color: var(--c-text-primary); +} +.preview-iframe { + display: block; + position: absolute; + top: 56px; + height: calc(100vh - 96px); + width: 100%; + z-index: 1; +} + .preview-iframe--layout { + height: calc(100vh - 56px); + } + .preview-iframe--text { + max-width: 600px; + transform: translateX(-50%); + left: 50%; + } +.icon { + display: inline-block; + background-image: url(../images/icon.png); + background-repeat: no-repeat; + background-position: center center; + background-size: 75%; + width: 22px; + height: 22px; + border-radius: 50%; + background-color: var(--c-postmark); +} +.u-alias { + display: block; + font-size: 12px; + color: var(--c-text-secondary); + font-weight: normal; +} + + +/* *************************** */ +/* Blank */ +/* *************************** */ + +.blank { + text-align: center; + margin: 60px auto; + max-width: 600px; +} +.blank p { + margin-bottom: 0; +} +.blank .snippet { + text-align: left; +} +.blank .snippet pre { + white-space: pre-wrap; +} + +/* *************************** */ +/* Instructions */ +/* *************************** */ + +.instructions { + margin: 0 auto; + max-width: 600px; +} +.instructions h4 { + text-align: center; + margin-bottom: 1.5em; +} +.instructions ol li { + margin-bottom: 1.5em; +} +.instructions ul li { + margin-bottom: .5em; +} +.instructions ul { + margin-top: .5em; +} + +/* *************************** */ +/* Utilities */ +/* *************************** */ + +.hidden { + display: none; +} +.f-code, +pre, +code { + font-family: 'Roboto Mono', monospace; +} +code { + padding: 2px 4px 3px; + color: var(--c-primary); + background-color: rgba(255,255,255); + border-radius: 4px; + font-size: 13px; + border: 1px solid rgba(0,0,0,.1); + white-space: nowrap; +} +.snippet { + margin-top: 10px; + padding: 10px; + color: var(--c-primary); + background-color: rgba(255,255,255); + border-radius: 4px; + font-size: 13px; + border: 1px solid rgba(0,0,0,.1); +} +.snippet pre { + margin: 0; + word-wrap: break-word; + word-break: break-all; +} +.bg-secondary { + background-color: var(--c-bg); +} diff --git a/preview/index.ejs b/preview/index.ejs index 3b2c85b..91b2ce2 100644 --- a/preview/index.ejs +++ b/preview/index.ejs @@ -1,54 +1,81 @@ <%- include("partials/head.ejs") %> - +
-

Postmark Templates

+

Postmark Templates

<%- path %>
-
-

<%- layouts.length %> Layouts

- +
+ <% if (layouts.length > 0) { %> +

<%- layouts.length %> <%- layouts.length > 1 ? 'Layouts' : 'Layout' %>

+ + <% } %> -

<%- templates.length %> Templates

<% if (templates.length > 0) { %> +

<%- templates.length %> <%- templates.length > 1 ? 'Templates' : 'Template' %>

+ <% } else { %> - meta.json -
-{
-  "Name": "Welcome",
-  "Alias": "welcome",
-  "Subject": "Your subject line",
+      
+ +

No templates were found

+

Pull your templates from Postmark:

+
postmark templates pull <%- path %>
+
+ +
+

Or build your templates locally

+
    +
  1. Create a new folder for your template: +
    +cd <%- path %>
    +mkdir password-reset
    +
    +
  2. +
  3. Your template folder should contain the following files: +
      +
    • content.html - HTML version
    • +
    • content.txt - Text version
    • +
    • meta.json - JSON containing the name, alias, subject, and type of template(Standard or Layout). +
      {
      +  "Name": "Password Reset",
      +  "Alias": "password-reset",
      +  "Subject": "Reset your password",
         "TemplateType": "Standard",
      -  "LayoutTemplate": "Enter the layout alias"
      -}
      -      
      + "LayoutTemplate": "layout-alias" +}

+ + + +
  • Refresh the page
  • + +
    + <% } %> diff --git a/preview/partials/head.ejs b/preview/partials/head.ejs index 3dce219..40b537f 100644 --- a/preview/partials/head.ejs +++ b/preview/partials/head.ejs @@ -4,5 +4,5 @@ Postmark template preview - + \ No newline at end of file diff --git a/preview/template.ejs b/preview/template.ejs index 27e0618..8e52b46 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -3,35 +3,37 @@ <%- include("partials/head.ejs") %>
    -
    +
    +
    +

    <%- template.Name %>

    + <%- template.Alias %> +
    -
    -

    - <%- template.Name %> - <%- template.Alias %> -

    -
    -
    +
    + <% if (template.TemplateType === 'Standard') { %> + Layout: <%- template.LayoutTemplate ? template.LayoutTemplate : 'None' %> + <% } %> +
    + <% if (template.TemplateType === 'Standard') {%> -
    -
    - Subject - <%- template.Subject || 'No subject' %> +
    +
    + Subject + <%- template.Subject || 'No subject' %> +
    -
    <% } %> - - + + - - + diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/prevew.ts index 8f0763a..8723e05 100644 --- a/src/commands/templates/prevew.ts +++ b/src/commands/templates/prevew.ts @@ -53,6 +53,8 @@ const preview = (args: TemplatePreviewArguments) => { app.use(express.static('preview/assets')) + // Template listing + app.get('/', (req, res) => { const manifest = createManifest(args.templatesdirectory) const templates = compileTemplates(manifest) @@ -62,32 +64,40 @@ const preview = (args: TemplatePreviewArguments) => { { templates: filter(templates, { TemplateType: 'Standard' }), layouts: filter(templates, { TemplateType: 'Layout' }), - path: untildify(args.templatesdirectory), + path: untildify(args.templatesdirectory).replace(/\/$/, ''), }, (err, html) => { - if (err) res.send(err) + if (err) return res.send(err) return res.send(html) } ) }) + // Get template by alias + app.get('/:alias', (req, res) => { const manifest = createManifest(args.templatesdirectory) const compiled = compileTemplates(manifest) const template: any = find(compiled, { Alias: req.params.alias }) - consolidate.ejs( - 'preview/template.ejs', - { template: template }, - (err, html) => { - if (err) res.send(err) - - return res.send(html) - } - ) + if (template) { + consolidate.ejs( + 'preview/template.ejs', + { template: template }, + (err, html) => { + if (err) return res.send(err) + + return res.send(html) + } + ) + } else { + res.redirect(301, '/') + } }) + // Return template HTML by alias + app.get('/html/:alias', (req, res) => { const manifest = createManifest(args.templatesdirectory) const compiled = compileTemplates(manifest) @@ -97,21 +107,24 @@ const preview = (args: TemplatePreviewArguments) => { return res.send(template.HtmlBody) } - return res.status(404) + return res.status(404).send('Not found!') }) + // Return template text by alias + app.get('/text/:alias', (req, res) => { const manifest = createManifest(args.templatesdirectory) const compiled = compileTemplates(manifest) const template: any = find(compiled, { Alias: req.params.alias }) + // res.send(200) if (template && template.TextBody) { res.set('Content-Type', 'text/plain') res.write(template.TextBody) return res.end() } - return res.status(404) + return res.status(404).send('Not found!') }) app.listen(port, () => @@ -126,14 +139,19 @@ const compileTemplates = (manifest: TemplateManifest[]) => { templates.forEach(template => { const layout = find(layouts, { Alias: template.LayoutTemplate || '' }) - - if (!layout) return - if (!layout.HtmlBody && !layout.TextBody) return + const html = template.HtmlBody || '' + const text = template.TextBody || '' compiled.push({ ...template, - HtmlBody: compileTemplate(layout.HtmlBody || '', template.HtmlBody || ''), - TextBody: compileTemplate(layout.TextBody || '', template.TextBody || ''), + HtmlBody: + layout && layout.HtmlBody + ? compileTemplate(layout.HtmlBody, html) + : html, + TextBody: + layout && layout.TextBody + ? compileTemplate(layout.TextBody, text) + : text, }) }) From 096bd61e572daeb896d92fb1bbaa8a803e0b3682 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 20 Nov 2019 14:24:55 -0700 Subject: [PATCH 07/37] Device viewport toggle --- preview/assets/images/responsive.svg | 8 +++ preview/assets/js/preview.js | 78 +++++++++++++++++++++---- preview/assets/styles/styles.css | 85 ++++++++++++++++++++++------ preview/index.ejs | 2 +- preview/template.ejs | 13 +++-- 5 files changed, 152 insertions(+), 34 deletions(-) create mode 100644 preview/assets/images/responsive.svg diff --git a/preview/assets/images/responsive.svg b/preview/assets/images/responsive.svg new file mode 100644 index 0000000..d68440c --- /dev/null +++ b/preview/assets/images/responsive.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/preview/assets/js/preview.js b/preview/assets/js/preview.js index b97f5fe..9a3cfc4 100644 --- a/preview/assets/js/preview.js +++ b/preview/assets/js/preview.js @@ -1,26 +1,82 @@ -var html = document.querySelector('.js-html') -var toggles = document.querySelectorAll('.js-toggle-mode') -var activeToggleClass = 'toggle_item--active' +var activeToggleClass = 'is-active' -toggles.forEach(function(toggle) { +/** + * Mode toggles + */ +var mode = document.querySelectorAll('.js-mode') +var currentMode = 'html' + +mode.forEach(function(toggle) { toggle.addEventListener('click', function(event) { event.preventDefault() - // Reset state - resetMode() + + // Reset all classes + mode.forEach(function(modeToggle) { + // Remove active class from all toggles + modeToggle.classList.remove(activeToggleClass) + + // Hide all views + document + .querySelector('.js-' + modeToggle.dataset.mode) + .classList.add('hidden') + }) // Add active class + currentMode = this.dataset.mode this.classList.add(activeToggleClass) - // Show preview + // Show view document .querySelector('.js-' + this.dataset.mode) .classList.remove('hidden') }) }) -function resetMode() { - toggles.forEach(function(toggle) { - toggle.classList.remove(activeToggleClass) - document.querySelector('.js-' + toggle.dataset.mode).classList.add('hidden') +/** + * View toggles + */ +var view = document.querySelectorAll('.js-view') +var currentView = 'desktop' + +view.forEach(function(toggle) { + // Add click event + toggle.addEventListener('click', function(event) { + event.preventDefault() + + view.forEach(function(viewToggle) { + // Remove active class from all toggles + viewToggle.classList.remove(activeToggleClass) + + document.querySelectorAll('.preview-iframe').forEach(function(item) { + item.classList.remove('preview-iframe--mobile') + }) + }) + + currentView = this.dataset.view + this.classList.add(activeToggleClass) + + if (this.dataset.view === 'mobile') { + document.querySelectorAll('.preview-iframe').forEach(function(item) { + item.classList.add('preview-iframe--mobile') + }) + } }) +}) + +function keypress(e) { + var evt = window.event ? event : e + + // ctrl + v + if (evt.keyCode == 86 && evt.ctrlKey) { + var view = currentView === 'desktop' ? 'mobile' : 'desktop' + document.querySelector('.js-view[data-view="' + view + '"]').click() + } + + // ctrl + m + if (evt.keyCode == 77 && evt.ctrlKey) { + var mode = currentMode === 'html' ? 'text' : 'html' + document.querySelector('.js-mode[data-mode="' + mode + '"]').click() + } } + +document.onkeydown = keypress diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index bbbf40b..781c0c4 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -13,6 +13,7 @@ body { font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 1rem; color: var(--c-text-primary); + background-color: var(--c-bg); } .container { margin: 0 auto; @@ -24,8 +25,7 @@ li { font-size: 16px; line-height: 1.5; } -a, -a:visited { +a { color: var(--c-text-primary); } @@ -47,7 +47,7 @@ a:visited { .toolbar { display: flex; justify-content: space-between; - align-items: center; + align-items: stretch; background-color: #FFF; height: 56px; box-shadow: 0 0 5px rgba(0, 0, 0, .1); @@ -58,11 +58,11 @@ a:visited { right: 0; z-index: 3; box-sizing: border-box; - padding: 0 15px; + padding: 10px 15px; } .toolbar_meta { display: flex; - align-items: center; + align-items: stretch; } .toolbar_title { display: flex; @@ -83,7 +83,8 @@ a:visited { .toolbar_back { text-decoration: none; border: 2px solid #dbdaeb; - padding: 4px 6px 6px; + margin: 2px 0; + padding: 3px 6px 0; border-radius: 3px; color: var(--c-text-secondary); transition: 100ms all ease-in-out; @@ -95,9 +96,9 @@ a:visited { } .toolbar_path { display: flex; - align-content: center; font-size: 12px; color: var(--c-text-secondary); + align-self: center; } .toolbar_path::before { display: inline-block; @@ -112,8 +113,9 @@ a:visited { .toolbar_icon { margin-right: 5px; } - .toolbar_layout { - margin-right: 10px; + + .toolbar .template-layout { + align-self: center; } @@ -125,18 +127,22 @@ a:visited { display: flex; justify-content: space-between; border: 2px solid #dbdaeb; - border-radius: 4px; + border-radius: 3px; + margin-left: 10px; + align-items: stretch; } .toggle_item { + display: flex; box-sizing: border-box; - display: inline-block; text-decoration: none; color: var(--c-text-secondary); - padding: 6px 12px; + padding: 0 12px; font-size: 13px; - margin: 3px 3px 3px; + margin: 3px; + border-radius: 2px; font-weight: bold; transition: 100ms all ease-in-out; + align-items: center; } .toggle_item:last-child { margin-left: 0; @@ -144,8 +150,8 @@ a:visited { .toggle_item:hover { color: var(--c-primary); } - .toggle_item--active, - .toggle_item--active:hover { + .toggle_item.is-active, + .toggle_item.is-active:hover { background-color: var(--c-primary); color: #FFF; border-radius: 2px; @@ -228,14 +234,24 @@ a:visited { height: calc(100vh - 96px); width: 100%; z-index: 1; + left: 50%; + transform: translateX(-50%); + box-shadow: 0 0 10px rgba(0,0,0,.1); + box-sizing: border-box; } .preview-iframe--layout { height: calc(100vh - 56px); } .preview-iframe--text { - max-width: 600px; + max-width: 650px; transform: translateX(-50%); left: 50%; + background-color: #FFF; + padding: 25px 40px 0; + font-family: 'Helvetica Neue'; + } + .preview-iframe--mobile { + width: 350px; } .icon { display: inline-block; @@ -332,6 +348,39 @@ code { word-wrap: break-word; word-break: break-all; } -.bg-secondary { - background-color: var(--c-bg); + + +.icon-view { + width: 40px; + padding: 0; +} +.icon-view::before { + display: inline-block; + content: ''; + background-image: url('../images/responsive.svg'); + margin: 0 auto; } + .icon-view--mobile::before { + width: 11px; + height: 16px; + } + .icon-view--mobile.is-active::before, + .icon-view--mobile.is-active:hover::before { + background-position: 0 -16px; + } + .icon-view--mobile:hover::before { + background-position: 0 -32px; + } + + .icon-view--desktop::before { + width: 18px; + height: 13px; + background-position: 18px 0; + } + .icon-view--desktop:hover::before { + background-position: 18px -32px; + } + .icon-view--desktop.is-active::before, + .icon-view--desktop.is-active:hover::before { + background-position: 18px -16px; + } \ No newline at end of file diff --git a/preview/index.ejs b/preview/index.ejs index 91b2ce2..da60b7d 100644 --- a/preview/index.ejs +++ b/preview/index.ejs @@ -1,7 +1,7 @@ <%- include("partials/head.ejs") %> - +

    Postmark Templates

    <%- path %> diff --git a/preview/template.ejs b/preview/template.ejs index 8e52b46..1dfca31 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -2,7 +2,7 @@ <%- include("partials/head.ejs") %> -
    +
    @@ -12,12 +12,17 @@
    <% if (template.TemplateType === 'Standard') { %> - Layout: <%- template.LayoutTemplate ? template.LayoutTemplate : 'None' %> + Layout: <%- template.LayoutTemplate ? template.LayoutTemplate : 'None' %> <% } %>
    - HTML - Text + + +
    + +
    + HTML + Text
    From e5d793a46d9d99e51eecca0da4399f008dab2e25 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 20 Nov 2019 16:22:58 -0700 Subject: [PATCH 08/37] Hot reloading --- package-lock.json | 300 +++++++++++++++++++++++++++++- package.json | 6 +- preview/assets/styles/styles.css | 3 +- preview/partials/hotReload.ejs | 10 + preview/template.ejs | 2 + src/commands/templates/helpers.ts | 109 +++++++++++ src/commands/templates/prevew.ts | 195 ++++++------------- src/commands/templates/push.ts | 67 +------ 8 files changed, 472 insertions(+), 220 deletions(-) create mode 100644 preview/partials/hotReload.ejs create mode 100644 src/commands/templates/helpers.ts diff --git a/package-lock.json b/package-lock.json index 52a39c5..284bcf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -149,8 +149,7 @@ "@types/node": { "version": "11.13.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.4.tgz", - "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==", - "dev": true + "integrity": "sha512-+rabAZZ3Yn7tF/XPGHupKIL5EcAbrLxnTr/hgQICxbeuAfWtT0UZSfULE+ndusckBItcv4o6ZeOJplQikVcLvQ==" }, "@types/ora": { "version": "3.2.0", @@ -224,6 +223,14 @@ "resolved": "https://registry.npmjs.org/@types/traverse/-/traverse-0.6.32.tgz", "integrity": "sha512-RBz2uRZVCXuMg93WD//aTS5B120QlT4lR/gL+935QtGsKHLS6sCtZBaKfWjIfk7ZXv/r8mtGbwjVIee6/3XTow==" }, + "@types/watch": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/watch/-/watch-1.0.1.tgz", + "integrity": "sha512-hN+2AA4LpM9So7z1ChhOiQ7SNZiKhrjKVDgxERT9nXTSb+ve1aNap4OExhNG4J/RAumJuZsg1kOeGlyYumVGew==", + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.0.tgz", @@ -316,6 +323,11 @@ "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", "dev": true }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", @@ -365,6 +377,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -395,6 +412,11 @@ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -410,12 +432,27 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -424,6 +461,19 @@ "tweetnacl": "^0.14.3" } }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, "bluebird": { "version": "3.5.5", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", @@ -493,6 +543,11 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -632,6 +687,21 @@ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -710,7 +780,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, "requires": { "ms": "^2.1.1" } @@ -815,6 +884,66 @@ "once": "^1.4.0" } }, + "engine.io": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.0.tgz", + "integrity": "sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "0.3.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "^7.1.2" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + } + } + }, + "engine.io-client": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz", + "integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", + "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -975,7 +1104,6 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", - "dev": true, "requires": { "merge": "^1.2.0" } @@ -1297,6 +1425,26 @@ } } }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1360,6 +1508,11 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1609,8 +1762,7 @@ "merge": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==" }, "merge-descriptors": { "version": "1.0.1", @@ -1657,8 +1809,7 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "mkdirp": { "version": "0.5.1", @@ -1892,6 +2043,11 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -2014,6 +2170,22 @@ "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=" }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2382,6 +2554,94 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "socket.io": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", + "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "requires": { + "debug": "~4.1.0", + "engine.io": "~3.4.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.3.0", + "socket.io-parser": "~3.4.0" + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" + }, + "socket.io-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", + "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "engine.io-client": "~3.4.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + } + } + }, + "socket.io-parser": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.0.tgz", + "integrity": "sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2537,6 +2797,11 @@ "os-tmpdir": "~1.0.2" } }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -2712,7 +2977,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/watch/-/watch-1.0.2.tgz", "integrity": "sha1-NApxe952Vyb6CqB9ch4BR6VR3ww=", - "dev": true, "requires": { "exec-sh": "^0.2.0", "minimist": "^1.2.0" @@ -2812,6 +3076,19 @@ "mkdirp": "^0.5.1" } }, + "ws": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz", + "integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==", + "requires": { + "async-limiter": "^1.0.0" + } + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", @@ -2909,6 +3186,11 @@ "decamelize": "^1.2.0" } }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + }, "yn": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", diff --git a/package.json b/package.json index 531a44f..729924e 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "./dist/index.js", "dependencies": { "@types/traverse": "^0.6.32", + "@types/watch": "^1.0.1", "chalk": "^2.4.2", "consolidate": "^0.15.1", "directory-tree": "^2.2.3", @@ -16,9 +17,11 @@ "ora": "^3.0.0", "postmark": "^2.2.7", "request": "^2.88.0", + "socket.io": "^2.3.0", "table": "^5.2.0", "traverse": "^0.6.6", "untildify": "^4.0.0", + "watch": "^1.0.2", "yargonaut": "^1.1.4", "yargs": "^13.2.4" }, @@ -44,8 +47,7 @@ "nconf": "^0.10.0", "pre-commit": "^1.2.2", "ts-node": "^8.0.3", - "typescript": "^3.4.3", - "watch": "^1.0.2" + "typescript": "^3.4.3" }, "scripts": { "start:dev": "watch 'npm run build' './src'", diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index 781c0c4..97ee7d4 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -238,6 +238,7 @@ a { transform: translateX(-50%); box-shadow: 0 0 10px rgba(0,0,0,.1); box-sizing: border-box; + transition: 200ms width ease-in-out; } .preview-iframe--layout { height: calc(100vh - 56px); @@ -251,7 +252,7 @@ a { font-family: 'Helvetica Neue'; } .preview-iframe--mobile { - width: 350px; + width: 360px; } .icon { display: inline-block; diff --git a/preview/partials/hotReload.ejs b/preview/partials/hotReload.ejs new file mode 100644 index 0000000..c0c6e97 --- /dev/null +++ b/preview/partials/hotReload.ejs @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/preview/template.ejs b/preview/template.ejs index 1dfca31..2a792e8 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -40,5 +40,7 @@ + + <%- include("partials/hotReload.ejs") %> diff --git a/src/commands/templates/helpers.ts b/src/commands/templates/helpers.ts new file mode 100644 index 0000000..2f4da70 --- /dev/null +++ b/src/commands/templates/helpers.ts @@ -0,0 +1,109 @@ +import { join, dirname } from 'path' +import { readJsonSync, readFileSync, existsSync } from 'fs-extra' +import { filter, find, replace } from 'lodash' +import traverse from 'traverse' +import dirTree from 'directory-tree' +import { TemplateManifest, MetaFileTraverse, MetaFile } from '../../types' + +/** + * Parses templates folder and files + */ + +export const createManifest = (path: string): TemplateManifest[] => { + let manifest: TemplateManifest[] = [] + + // Return empty array if path does not exist + if (!existsSync(path)) return manifest + + // Find meta files and flatten into collection + const list: MetaFileTraverse[] = findMetaFiles(path) + + // Parse each directory + list.forEach(file => { + const item = createManifestItem(file) + if (item) manifest.push(item) + }) + + return manifest +} + +/** + * Searches for all metadata files and flattens into a collection + */ +export const findMetaFiles = (path: string): MetaFileTraverse[] => + traverse(dirTree(path)).reduce((acc, file) => { + if (file.name === 'meta.json') acc.push(file) + return acc + }, []) + +/** + * Gathers the template's content and metadata based on the metadata file location + */ +export const createManifestItem = (file: any): MetaFile | null => { + const { path } = file // Path to meta file + const rootPath = dirname(path) // Folder path + const htmlPath = join(rootPath, 'content.html') // HTML path + const textPath = join(rootPath, 'content.txt') // Text path + + // Check if meta file exists + if (existsSync(path)) { + const metaFile: MetaFile = readJsonSync(path) + const htmlFile: string = existsSync(htmlPath) + ? readFileSync(htmlPath, 'utf-8') + : '' + const textFile: string = existsSync(textPath) + ? readFileSync(textPath, 'utf-8') + : '' + + return { + HtmlBody: htmlFile, + TextBody: textFile, + ...metaFile, + } + } + + return null +} + +/** + * Combine all templates and layouts + */ +export const compileTemplates = (manifest: TemplateManifest[]) => { + let compiled: any = [] + const layouts = filter(manifest, { TemplateType: 'Layout' }) + const templates = filter(manifest, { TemplateType: 'Standard' }) + + templates.forEach(template => { + const layout = find(layouts, { Alias: template.LayoutTemplate || '' }) + const html = template.HtmlBody || '' + const text = template.TextBody || '' + + compiled.push({ + ...template, + HtmlBody: + layout && layout.HtmlBody + ? compileTemplate(layout.HtmlBody, html) + : html, + TextBody: + layout && layout.TextBody + ? compileTemplate(layout.TextBody, text) + : text, + }) + }) + + layouts.forEach(layout => { + if (!layout.HtmlBody && !layout.TextBody) return + const content = 'Template content is inserted here.' + + compiled.push({ + ...layout, + HtmlBody: compileTemplate(layout.HtmlBody || '', content), + TextBody: compileTemplate(layout.TextBody || '', content), + }) + }) + + return compiled +} + +export const compileTemplate = (layout: string, template: string): string => + replace(layout, /({{{)(.?@content.?)(}}})/g, template) diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/prevew.ts index 8723e05..d27fe6a 100644 --- a/src/commands/templates/prevew.ts +++ b/src/commands/templates/prevew.ts @@ -1,17 +1,11 @@ -import { join, dirname } from 'path' -import { readJsonSync, readFileSync, existsSync } from 'fs-extra' -import traverse from 'traverse' -import { filter, find, replace } from 'lodash' +import { existsSync } from 'fs-extra' +import { filter, find } from 'lodash' import untildify from 'untildify' -import dirTree from 'directory-tree' import express from 'express' +import { createMonitor } from 'watch' import consolidate from 'consolidate' -import { - TemplatePreviewArguments, - TemplateManifest, - MetaFileTraverse, - MetaFile, -} from '../../types' +import { createManifest, compileTemplates } from './helpers' +import { TemplatePreviewArguments } from '../../types' import { log } from '../../utils' export const command = 'preview [options]' @@ -31,7 +25,6 @@ export const handler = (args: TemplatePreviewArguments) => exec(args) */ const exec = (args: TemplatePreviewArguments): void => validateDirectory(args) -// TODO: we can probably reuse this with push.ts const validateDirectory = (args: TemplatePreviewArguments) => { const rootPath: string = untildify(args.templatesdirectory) @@ -48,24 +41,45 @@ const validateDirectory = (args: TemplatePreviewArguments) => { * Preview */ const preview = (args: TemplatePreviewArguments) => { + const { port, templatesdirectory } = args const app = express() - const port = args.port + const server = require('http').createServer(app) + const io = require('socket.io')(server) + // Cache manifest and compiled templates + let manifest = createManifest(templatesdirectory) + let compiled = compileTemplates(manifest) + + // Static assets app.use(express.static('preview/assets')) - // Template listing + // Update manifest when files change + createMonitor(untildify(templatesdirectory), { interval: 1 }, monitor => { + function eventHandler() { + // Update manifest and compiled templates + manifest = createManifest(templatesdirectory) + compiled = compileTemplates(manifest) + + // Trigger reload on client + io.emit('change') + } + + monitor.on('created', eventHandler) + monitor.on('changed', eventHandler) + monitor.on('removed', eventHandler) + process.on('exit', () => monitor.stop()) + }) + + // Template listing app.get('/', (req, res) => { - const manifest = createManifest(args.templatesdirectory) - const templates = compileTemplates(manifest) + const templates = filter(manifest, { TemplateType: 'Standard' }) + const layouts = filter(manifest, { TemplateType: 'Layout' }) + const path = untildify(templatesdirectory).replace(/\/$/, '') consolidate.ejs( 'preview/index.ejs', - { - templates: filter(templates, { TemplateType: 'Standard' }), - layouts: filter(templates, { TemplateType: 'Layout' }), - path: untildify(args.templatesdirectory).replace(/\/$/, ''), - }, + { templates, layouts, path }, (err, html) => { if (err) return res.send(err) @@ -74,32 +88,28 @@ const preview = (args: TemplatePreviewArguments) => { ) }) - // Get template by alias - + /** + * Get template by alias + */ app.get('/:alias', (req, res) => { - const manifest = createManifest(args.templatesdirectory) - const compiled = compileTemplates(manifest) - const template: any = find(compiled, { Alias: req.params.alias }) + const template: any = find(manifest, { Alias: req.params.alias }) if (template) { - consolidate.ejs( - 'preview/template.ejs', - { template: template }, - (err, html) => { - if (err) return res.send(err) - - return res.send(html) - } - ) + consolidate.ejs('preview/template.ejs', { template }, (err, html) => { + if (err) return res.send(err) + + return res.send(html) + }) } else { + // Redirect to index res.redirect(301, '/') } }) - // Return template HTML by alias - + /** + * Get template HTML version by alias + */ app.get('/html/:alias', (req, res) => { - const manifest = createManifest(args.templatesdirectory) const compiled = compileTemplates(manifest) const template: any = find(compiled, { Alias: req.params.alias }) @@ -110,14 +120,12 @@ const preview = (args: TemplatePreviewArguments) => { return res.status(404).send('Not found!') }) - // Return template text by alias - + /** + * Get template text version by alias + */ app.get('/text/:alias', (req, res) => { - const manifest = createManifest(args.templatesdirectory) - const compiled = compileTemplates(manifest) const template: any = find(compiled, { Alias: req.params.alias }) - // res.send(200) if (template && template.TextBody) { res.set('Content-Type', 'text/plain') res.write(template.TextBody) @@ -127,106 +135,7 @@ const preview = (args: TemplatePreviewArguments) => { return res.status(404).send('Not found!') }) - app.listen(port, () => + server.listen(port, () => console.log(`Previews available at http://localhost:${port}!`) ) } - -const compileTemplates = (manifest: TemplateManifest[]) => { - let compiled: any = [] - const layouts = filter(manifest, { TemplateType: 'Layout' }) - const templates = filter(manifest, { TemplateType: 'Standard' }) - - templates.forEach(template => { - const layout = find(layouts, { Alias: template.LayoutTemplate || '' }) - const html = template.HtmlBody || '' - const text = template.TextBody || '' - - compiled.push({ - ...template, - HtmlBody: - layout && layout.HtmlBody - ? compileTemplate(layout.HtmlBody, html) - : html, - TextBody: - layout && layout.TextBody - ? compileTemplate(layout.TextBody, text) - : text, - }) - }) - - layouts.forEach(layout => { - if (!layout.HtmlBody && !layout.TextBody) return - const content = 'Template content is inserted here.' - - compiled.push({ - ...layout, - HtmlBody: compileTemplate(layout.HtmlBody || '', content), - TextBody: compileTemplate(layout.TextBody || '', content), - }) - }) - - return compiled -} - -const compileTemplate = (layout: string, template: string): string => - replace(layout, /({{{)(.?@content.?)(}}})/g, template) - -/** - * Parses templates folder and files - */ -const createManifest = (path: string): TemplateManifest[] => { - let manifest: TemplateManifest[] = [] - - // Return empty array if path does not exist - if (!existsSync(path)) return manifest - - // Find meta files and flatten into collection - const list: MetaFileTraverse[] = FindMetaFiles(path) - - // Parse each directory - list.forEach(file => { - const item = createManifestItem(file) - if (item) manifest.push(item) - }) - - return manifest -} - -/** - * Gathers the template's content and metadata based on the metadata file location - */ -const createManifestItem = (file: any): MetaFile | null => { - const { path } = file // Path to meta file - const rootPath = dirname(path) // Folder path - const htmlPath = join(rootPath, 'content.html') // HTML path - const textPath = join(rootPath, 'content.txt') // Text path - - // Check if meta file exists - if (existsSync(path)) { - const metaFile: MetaFile = readJsonSync(path) - const htmlFile: string = existsSync(htmlPath) - ? readFileSync(htmlPath, 'utf-8') - : '' - const textFile: string = existsSync(textPath) - ? readFileSync(textPath, 'utf-8') - : '' - - return { - HtmlBody: htmlFile, - TextBody: textFile, - ...metaFile, - } - } - - return null -} - -/** - * Searches for all metadata files and flattens into a collection - */ -const FindMetaFiles = (path: string): MetaFileTraverse[] => - traverse(dirTree(path)).reduce((acc, file) => { - if (file.name === 'meta.json') acc.push(file) - return acc - }, []) diff --git a/src/commands/templates/push.ts b/src/commands/templates/push.ts index c8cb93f..35f22ea 100644 --- a/src/commands/templates/push.ts +++ b/src/commands/templates/push.ts @@ -1,13 +1,11 @@ import chalk from 'chalk' import ora from 'ora' -import { join, dirname } from 'path' import { find } from 'lodash' import { prompt } from 'inquirer' -import traverse from 'traverse' import { table, getBorderCharacters } from 'table' import untildify from 'untildify' -import { readJsonSync, readFileSync, existsSync } from 'fs-extra' -import dirTree from 'directory-tree' +import { existsSync } from 'fs-extra' +import { createManifest } from './helpers' import { ServerClient } from 'postmark' import { TemplateManifest, @@ -15,8 +13,6 @@ import { TemplatePushReview, TemplatePushArguments, Templates, - MetaFileTraverse, - MetaFile, } from '../../types' import { pluralize, log, validateToken } from '../../utils' @@ -196,65 +192,6 @@ const layoutUsedLabel = ( return label } -/** - * Parses templates folder and files - */ -const createManifest = (path: string): TemplateManifest[] => { - let manifest: TemplateManifest[] = [] - - // Return empty array if path does not exist - if (!existsSync(path)) return manifest - - // Find meta files and flatten into collection - const list: MetaFileTraverse[] = FindMetaFiles(path) - - // Parse each directory - list.forEach(file => { - const item = createManifestItem(file) - if (item) manifest.push(item) - }) - - return manifest -} - -/** - * Gathers the template's content and metadata based on the metadata file location - */ -const createManifestItem = (file: any): MetaFile | null => { - const { path } = file // Path to meta file - const rootPath = dirname(path) // Folder path - const htmlPath = join(rootPath, 'content.html') // HTML path - const textPath = join(rootPath, 'content.txt') // Text path - - // Check if meta file exists - if (existsSync(path)) { - const metaFile: MetaFile = readJsonSync(path) - const htmlFile: string = existsSync(htmlPath) - ? readFileSync(htmlPath, 'utf-8') - : '' - const textFile: string = existsSync(textPath) - ? readFileSync(textPath, 'utf-8') - : '' - - return { - HtmlBody: htmlFile, - TextBody: textFile, - ...metaFile, - } - } - - return null -} - -/** - * Searches for all metadata files and flattens into a collection - */ -const FindMetaFiles = (path: string): MetaFileTraverse[] => - traverse(dirTree(path)).reduce((acc, file) => { - if (file.name === 'meta.json') acc.push(file) - return acc - }, []) - /** * Show which templates will change after the publish */ From 3d342a3eed1b2f0317ef8b4da33bd83d6456d154 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 20 Nov 2019 18:00:29 -0700 Subject: [PATCH 09/37] Reload label --- preview/assets/images/check.svg | 9 ++++ preview/assets/styles/styles.css | 49 ++++++++++++++++++- .../{hotReload.ejs => previewScripts.ejs} | 9 ++++ preview/template.ejs | 5 +- src/commands/templates/prevew.ts | 4 +- 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 preview/assets/images/check.svg rename preview/partials/{hotReload.ejs => previewScripts.ejs} (56%) diff --git a/preview/assets/images/check.svg b/preview/assets/images/check.svg new file mode 100644 index 0000000..848a33f --- /dev/null +++ b/preview/assets/images/check.svg @@ -0,0 +1,9 @@ + + + + check + Created with Sketch. + + + + \ No newline at end of file diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index 97ee7d4..8b26a8e 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -50,7 +50,7 @@ a { align-items: stretch; background-color: #FFF; height: 56px; - box-shadow: 0 0 5px rgba(0, 0, 0, .1); + box-shadow: 0 0 5px rgba(0, 0, 0, .2); box-sizing: border-box; position: fixed; top: 0; @@ -214,7 +214,7 @@ a { z-index: 2; box-sizing: border-box; padding: 0 15px; - box-shadow: 0 0 5px rgba(0, 0, 0, .1); + box-shadow: 0 0 5px rgba(0, 0, 0, .2); } .subject { font-size: 13px; @@ -384,4 +384,49 @@ code { .icon-view--desktop.is-active::before, .icon-view--desktop.is-active:hover::before { background-position: 18px -16px; + } + + + .reloaded { + display: flex; + align-items: center; + font-size: 13px; + transition: 200ms all ease-in-out; + transform: translateY(4px); + opacity: 0; + font-weight: bold; + color: var(--c-primary); + /* background-image: url('../images/check.svg'); */ + /* background-repeat: no-repeat; */ + /* padding-left: 18px; */ + /* display: flex; */ + } + .reloaded::before { + content: ''; + display: inline-block; + background-image: url('../images/check.svg'); + background-size: 100%; + width: 15px; + height: 15px; + margin-right: 4px; + } + .reloaded.is-active { + transform: translateY(0); + opacity: 1; + } + .reloaded.is-active::before { + animation: 280ms 500ms 1 linear spinzoom; + } + + @keyframes spinzoom { + 0% { + transform: rotate(0deg) scale(1); + } + 50% { + transform: rotate(220deg) scale(1.6); + filter: blur(.5px); + } + 100% { + transform: rotate(359deg) scale(1); + } } \ No newline at end of file diff --git a/preview/partials/hotReload.ejs b/preview/partials/previewScripts.ejs similarity index 56% rename from preview/partials/hotReload.ejs rename to preview/partials/previewScripts.ejs index c0c6e97..d706f36 100644 --- a/preview/partials/hotReload.ejs +++ b/preview/partials/previewScripts.ejs @@ -1,3 +1,5 @@ + + \ No newline at end of file diff --git a/preview/template.ejs b/preview/template.ejs index 2a792e8..ace3d7a 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -33,14 +33,13 @@ Subject <%- template.Subject || 'No subject' %>
    +
    Reloaded!
    <% } %> - - - <%- include("partials/hotReload.ejs") %> + <%- include("partials/previewScripts.ejs") %> diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/prevew.ts index d27fe6a..e31349e 100644 --- a/src/commands/templates/prevew.ts +++ b/src/commands/templates/prevew.ts @@ -54,7 +54,7 @@ const preview = (args: TemplatePreviewArguments) => { app.use(express.static('preview/assets')) // Update manifest when files change - createMonitor(untildify(templatesdirectory), { interval: 1 }, monitor => { + createMonitor(untildify(templatesdirectory), { interval: 2 }, monitor => { function eventHandler() { // Update manifest and compiled templates manifest = createManifest(templatesdirectory) @@ -64,10 +64,12 @@ const preview = (args: TemplatePreviewArguments) => { io.emit('change') } + // Monitor all events monitor.on('created', eventHandler) monitor.on('changed', eventHandler) monitor.on('removed', eventHandler) + // Stop monitor when process exists process.on('exit', () => monitor.stop()) }) From 99171ddadea00275b56af3d50018f4ab12a8fa08 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 20 Nov 2019 18:01:51 -0700 Subject: [PATCH 10/37] Optimize SVGs --- preview/assets/images/check.svg | 10 +-------- preview/assets/images/responsive.svg | 9 +------- preview/assets/images/templates.svg | 33 +--------------------------- 3 files changed, 3 insertions(+), 49 deletions(-) diff --git a/preview/assets/images/check.svg b/preview/assets/images/check.svg index 848a33f..7148849 100644 --- a/preview/assets/images/check.svg +++ b/preview/assets/images/check.svg @@ -1,9 +1 @@ - - - - check - Created with Sketch. - - - - \ No newline at end of file + \ No newline at end of file diff --git a/preview/assets/images/responsive.svg b/preview/assets/images/responsive.svg index d68440c..27abd06 100644 --- a/preview/assets/images/responsive.svg +++ b/preview/assets/images/responsive.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/preview/assets/images/templates.svg b/preview/assets/images/templates.svg index d512161..1ff9936 100644 --- a/preview/assets/images/templates.svg +++ b/preview/assets/images/templates.svg @@ -1,32 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file From 6d48e9676a389dd8b2484c3907ffcc4f938e5075 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 08:36:45 -0700 Subject: [PATCH 11/37] Text version styling --- preview/assets/styles/styles.css | 162 +++++++++++++++++++++---------- preview/templateText.ejs | 14 +++ src/commands/templates/prevew.ts | 16 ++- 3 files changed, 136 insertions(+), 56 deletions(-) create mode 100644 preview/templateText.ejs diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index 8b26a8e..cde6075 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -6,25 +6,31 @@ --c-text-primary: #3d3f4e; --c-text-secondary: #8e91a6; --c-postmark: #FFDE00; + + --f-primary: 'Roboto', Helvetica, Arial, sans-serif; + --f-code: 'Roboto Mono', monospace; } body { background-color: #FFF; - font-family: 'Roboto', Helvetica, Arial, sans-serif; + font-family: var(--f-primary); font-size: 1rem; color: var(--c-text-primary); background-color: var(--c-bg); } + .container { margin: 0 auto; padding: 56px 0; max-width: 800px; } + p, li { font-size: 16px; line-height: 1.5; } + a { color: var(--c-text-primary); } @@ -41,7 +47,7 @@ a { /* *************************** */ -/** Toolbar **/ +/** Toolbars **/ /* *************************** */ .toolbar { @@ -60,10 +66,12 @@ a { box-sizing: border-box; padding: 10px 15px; } + .toolbar_meta { display: flex; align-items: stretch; } + .toolbar_title { display: flex; font-size: 14px; @@ -71,15 +79,19 @@ a { margin: 0; align-items: center; } + .toolbar_title--index { font-size: 16px; } + .toolbar_alias { margin-top: 4px; } + .toolbar_text { margin: 0 0 0 10px; } + .toolbar_back { text-decoration: none; border: 2px solid #dbdaeb; @@ -89,17 +101,20 @@ a { color: var(--c-text-secondary); transition: 100ms all ease-in-out; } + .toolbar_back:hover { color: var(--c-primary); background-color: #fff2f2; border-color: var(--c-primary); } + .toolbar_path { display: flex; font-size: 12px; color: var(--c-text-secondary); align-self: center; } + .toolbar_path::before { display: inline-block; content: ''; @@ -110,6 +125,7 @@ a { height: 12px; margin-right: 5px; } + .toolbar_icon { margin-right: 5px; } @@ -118,6 +134,23 @@ a { align-self: center; } +.sub-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #FFF; + height: 40px; + box-sizing: border-box; + position: absolute; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + box-sizing: border-box; + padding: 0 15px; + box-shadow: 0 0 5px rgba(0, 0, 0, .2); +} + /* *************************** */ /* Toggle button */ @@ -131,6 +164,7 @@ a { margin-left: 10px; align-items: stretch; } + .toggle_item { display: flex; box-sizing: border-box; @@ -144,12 +178,15 @@ a { transition: 100ms all ease-in-out; align-items: center; } + .toggle_item:last-child { margin-left: 0; } + .toggle_item:hover { color: var(--c-primary); } + .toggle_item.is-active, .toggle_item.is-active:hover { background-color: var(--c-primary); @@ -165,6 +202,7 @@ a { margin: 0 auto; width: 80%; } + .template-link { display: flex; justify-content: space-between; @@ -179,10 +217,11 @@ a { transition: 100ms all ease-in-out; align-items: center; } + .template-link:hover { border: 2px solid var(--c-primary); background-color: #fff2f2; - color: var(--c-primary) !important; + color: var(--c-primary); } .template-title { @@ -190,6 +229,7 @@ a { font-size: 16px; line-height: 1.4; } + .template-layout { color: var(--c-text-secondary); font-size: 12px; @@ -197,36 +237,9 @@ a { /* *************************** */ -/* Misc */ +/* Previews */ /* *************************** */ -.sub-toolbar { - display: flex; - justify-content: space-between; - align-items: center; - background-color: #FFF; - height: 40px; - box-sizing: border-box; - position: absolute; - bottom: 0; - left: 0; - right: 0; - z-index: 2; - box-sizing: border-box; - padding: 0 15px; - box-shadow: 0 0 5px rgba(0, 0, 0, .2); -} -.subject { - font-size: 13px; - font-weight: normal; -} -.subject_label { - color: var(--c-text-secondary); - margin-right: 8px; -} -.subject_line { - color: var(--c-text-primary); -} .preview-iframe { display: block; position: absolute; @@ -240,20 +253,56 @@ a { box-sizing: border-box; transition: 200ms width ease-in-out; } + .preview-iframe--layout { height: calc(100vh - 56px); } + .preview-iframe--text { max-width: 650px; transform: translateX(-50%); left: 50%; background-color: #FFF; - padding: 25px 40px 0; - font-family: 'Helvetica Neue'; } + .preview-iframe--mobile { width: 360px; } + +.preview-text { + padding: 50px; + background-color: #FFF; +} + + .preview-text pre { + margin: 0; + font-family: var(--f-primary); + word-wrap: break-word; + white-space: pre-wrap; + font-size: 16px; + line-height: 1.4; + color: #000; + } + + +/* *************************** */ +/* Misc */ +/* *************************** */ + +.subject { + font-size: 13px; + font-weight: normal; +} + + .subject_label { + color: var(--c-text-secondary); + margin-right: 8px; + } + + .subject_line { + color: var(--c-text-primary); + } + .icon { display: inline-block; background-image: url(../images/icon.png); @@ -265,6 +314,7 @@ a { border-radius: 50%; background-color: var(--c-postmark); } + .u-alias { display: block; font-size: 12px; @@ -300,19 +350,19 @@ a { margin: 0 auto; max-width: 600px; } -.instructions h4 { - text-align: center; - margin-bottom: 1.5em; -} -.instructions ol li { - margin-bottom: 1.5em; -} -.instructions ul li { - margin-bottom: .5em; -} -.instructions ul { - margin-top: .5em; -} + .instructions h4 { + text-align: center; + margin-bottom: 1.5em; + } + .instructions ol li { + margin-bottom: 1.5em; + } + .instructions ul li { + margin-bottom: .5em; + } + .instructions ul { + margin-top: .5em; + } /* *************************** */ /* Utilities */ @@ -321,11 +371,13 @@ a { .hidden { display: none; } + .f-code, pre, code { - font-family: 'Roboto Mono', monospace; + font-family: var(--f-code); } + code { padding: 2px 4px 3px; color: var(--c-primary); @@ -335,6 +387,7 @@ code { border: 1px solid rgba(0,0,0,.1); white-space: nowrap; } + .snippet { margin-top: 10px; padding: 10px; @@ -344,6 +397,7 @@ code { font-size: 13px; border: 1px solid rgba(0,0,0,.1); } + .snippet pre { margin: 0; word-wrap: break-word; @@ -361,26 +415,33 @@ code { background-image: url('../images/responsive.svg'); margin: 0 auto; } + + /* Mobile icon */ .icon-view--mobile::before { width: 11px; height: 16px; } + .icon-view--mobile.is-active::before, .icon-view--mobile.is-active:hover::before { background-position: 0 -16px; } + .icon-view--mobile:hover::before { background-position: 0 -32px; } + /* Desktop icon */ .icon-view--desktop::before { width: 18px; height: 13px; background-position: 18px 0; } + .icon-view--desktop:hover::before { background-position: 18px -32px; } + .icon-view--desktop.is-active::before, .icon-view--desktop.is-active:hover::before { background-position: 18px -16px; @@ -396,11 +457,8 @@ code { opacity: 0; font-weight: bold; color: var(--c-primary); - /* background-image: url('../images/check.svg'); */ - /* background-repeat: no-repeat; */ - /* padding-left: 18px; */ - /* display: flex; */ } + .reloaded::before { content: ''; display: inline-block; @@ -410,10 +468,12 @@ code { height: 15px; margin-right: 4px; } + .reloaded.is-active { transform: translateY(0); opacity: 1; } + .reloaded.is-active::before { animation: 280ms 500ms 1 linear spinzoom; } diff --git a/preview/templateText.ejs b/preview/templateText.ejs new file mode 100644 index 0000000..e1073d3 --- /dev/null +++ b/preview/templateText.ejs @@ -0,0 +1,14 @@ + + + + + + + +
    +
    +    <%- body %>
    +  
    +
    + + \ No newline at end of file diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/prevew.ts index e31349e..10e488e 100644 --- a/src/commands/templates/prevew.ts +++ b/src/commands/templates/prevew.ts @@ -129,12 +129,18 @@ const preview = (args: TemplatePreviewArguments) => { const template: any = find(compiled, { Alias: req.params.alias }) if (template && template.TextBody) { - res.set('Content-Type', 'text/plain') - res.write(template.TextBody) - return res.end() + consolidate.ejs( + 'preview/templateText.ejs', + { body: template.TextBody }, + (err, html) => { + if (err) return res.send(err) + + return res.send(html) + } + ) + } else { + return res.status(404).send('Not found!') } - - return res.status(404).send('Not found!') }) server.listen(port, () => From c12a5e1479b558d7751a1b41456aba8c18cd2b79 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 10:24:04 -0700 Subject: [PATCH 12/37] UI tweaks --- preview/assets/images/folder.svg | 2 +- preview/assets/images/responsive.svg | 2 +- preview/assets/styles/styles.css | 23 ++++++++++++++--------- preview/template.ejs | 8 +++++++- preview/templateText.ejs | 8 ++------ 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/preview/assets/images/folder.svg b/preview/assets/images/folder.svg index dace5be..80a41b5 100644 --- a/preview/assets/images/folder.svg +++ b/preview/assets/images/folder.svg @@ -1 +1 @@ -folderCreated with Sketch. \ No newline at end of file + \ No newline at end of file diff --git a/preview/assets/images/responsive.svg b/preview/assets/images/responsive.svg index 27abd06..c6ca25f 100644 --- a/preview/assets/images/responsive.svg +++ b/preview/assets/images/responsive.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index cde6075..392711d 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -1,12 +1,15 @@ @import url("reset.css"); :root { + /* Colors */ --c-primary: #ff7c83; + --c-primary-transparent: #fff2f2; --c-bg: #eeeff6; --c-text-primary: #3d3f4e; --c-text-secondary: #8e91a6; --c-postmark: #FFDE00; + /* Fonts */ --f-primary: 'Roboto', Helvetica, Arial, sans-serif; --f-code: 'Roboto Mono', monospace; } @@ -104,7 +107,7 @@ a { .toolbar_back:hover { color: var(--c-primary); - background-color: #fff2f2; + background-color: var(--c-primary-transparent); border-color: var(--c-primary); } @@ -127,11 +130,12 @@ a { } .toolbar_icon { - margin-right: 5px; + margin-right: 8px; } .toolbar .template-layout { align-self: center; + margin-right: 5px; } .sub-toolbar { @@ -220,7 +224,7 @@ a { .template-link:hover { border: 2px solid var(--c-primary); - background-color: #fff2f2; + background-color: var(--c-primary-transparent); color: var(--c-primary); } @@ -230,7 +234,8 @@ a { line-height: 1.4; } -.template-layout { +.template-layout, +.template-layout a { color: var(--c-text-secondary); font-size: 12px; } @@ -279,8 +284,8 @@ a { font-family: var(--f-primary); word-wrap: break-word; white-space: pre-wrap; - font-size: 16px; - line-height: 1.4; + font-size: 14px; + line-height: 1.5; color: #000; } @@ -311,7 +316,7 @@ a { background-size: 75%; width: 22px; height: 22px; - border-radius: 50%; + border-radius: 3px; background-color: var(--c-postmark); } @@ -381,7 +386,7 @@ code { code { padding: 2px 4px 3px; color: var(--c-primary); - background-color: rgba(255,255,255); + background-color: #FFF; border-radius: 4px; font-size: 13px; border: 1px solid rgba(0,0,0,.1); @@ -392,7 +397,7 @@ code { margin-top: 10px; padding: 10px; color: var(--c-primary); - background-color: rgba(255,255,255); + background-color: #FFF; border-radius: 4px; font-size: 13px; border: 1px solid rgba(0,0,0,.1); diff --git a/preview/template.ejs b/preview/template.ejs index ace3d7a..551ea87 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -12,7 +12,13 @@
    <% if (template.TemplateType === 'Standard') { %> - Layout: <%- template.LayoutTemplate ? template.LayoutTemplate : 'None' %> + Layout: + <% if (template.LayoutTemplate) { %> + <%- template.LayoutTemplate %> + <% } else { %> + None + <% } %> + <% } %>
    diff --git a/preview/templateText.ejs b/preview/templateText.ejs index e1073d3..c7e1468 100644 --- a/preview/templateText.ejs +++ b/preview/templateText.ejs @@ -4,11 +4,7 @@ - -
    -
    -    <%- body %>
    -  
    -
    + +
    <%- body %>
    \ No newline at end of file From 1fa9adcf5360c32ee617aff1ac13d816f9d976ee Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 10:24:19 -0700 Subject: [PATCH 13/37] Fix typo --- src/commands/templates/{prevew.ts => preview.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/commands/templates/{prevew.ts => preview.ts} (100%) diff --git a/src/commands/templates/prevew.ts b/src/commands/templates/preview.ts similarity index 100% rename from src/commands/templates/prevew.ts rename to src/commands/templates/preview.ts From e01f0f0228457b13327c00083504a8fcaad41c49 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 11:25:06 -0700 Subject: [PATCH 14/37] Improve logging --- src/commands/templates/preview.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index 10e488e..26dec90 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -1,3 +1,4 @@ +import chalk from 'chalk' import { existsSync } from 'fs-extra' import { filter, find } from 'lodash' import untildify from 'untildify' @@ -42,6 +43,7 @@ const validateDirectory = (args: TemplatePreviewArguments) => { */ const preview = (args: TemplatePreviewArguments) => { const { port, templatesdirectory } = args + console.log(`${title} Starting template preview server...`) const app = express() const server = require('http').createServer(app) const io = require('socket.io')(server) @@ -60,6 +62,7 @@ const preview = (args: TemplatePreviewArguments) => { manifest = createManifest(templatesdirectory) compiled = compileTemplates(manifest) + console.log(`${title} File changed. Reloading browser...`) // Trigger reload on client io.emit('change') } @@ -68,9 +71,6 @@ const preview = (args: TemplatePreviewArguments) => { monitor.on('created', eventHandler) monitor.on('changed', eventHandler) monitor.on('removed', eventHandler) - - // Stop monitor when process exists - process.on('exit', () => monitor.stop()) }) // Template listing @@ -143,7 +143,16 @@ const preview = (args: TemplatePreviewArguments) => { } }) - server.listen(port, () => - console.log(`Previews available at http://localhost:${port}!`) - ) + server.listen(port, () => { + console.log(`${title} Template preview server ready. Happy coding!`) + + console.log(divider) + console.log(`URL: ${chalk.green(`http://localhost:${port}`)}`) + console.log(divider) + }) } + +const title = `${chalk.gray('::')}${chalk.yellow('Postmark')}${chalk.gray( + '::' +)}` +const divider = chalk.gray(':'.repeat(34)) From 9c86f674dc1c6c9dee708ca96dd023efb6a0942f Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 11:43:10 -0700 Subject: [PATCH 15/37] Tweak logging --- src/commands/templates/preview.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index 26dec90..c27f6d1 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -152,7 +152,5 @@ const preview = (args: TemplatePreviewArguments) => { }) } -const title = `${chalk.gray('::')}${chalk.yellow('Postmark')}${chalk.gray( - '::' -)}` -const divider = chalk.gray(':'.repeat(34)) +const title = `${chalk.yellow('ミ▢ Postmark')}${chalk.gray(':')}` +const divider = chalk.gray('-'.repeat(34)) From 9a942cace41fe6812776b42712d2034f8726549a Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 11:44:00 -0700 Subject: [PATCH 16/37] Bump minor version (1.3.2) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 284bcf6..0450416 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "postmark-cli", - "version": "1.2.2", + "version": "1.3.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 729924e..4df820c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postmark-cli", - "version": "1.2.2", + "version": "1.3.2", "description": "A CLI tool for managing templates, sending emails, and fetching servers on Postmark.", "main": "./dist/index.js", "dependencies": { From 486a36b792afd84c0fe4995a69b1c20e9438556c Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 12:11:46 -0700 Subject: [PATCH 17/37] Import fonts via CSS --- preview/assets/styles/styles.css | 1 + preview/partials/head.ejs | 1 - preview/templateText.ejs | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index 392711d..e8d161a 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -1,3 +1,4 @@ +@import url('https://fonts.googleapis.com/css?family=Roboto+Mono|Roboto:400,700&display=swap'); @import url("reset.css"); :root { diff --git a/preview/partials/head.ejs b/preview/partials/head.ejs index 40b537f..aeac5ac 100644 --- a/preview/partials/head.ejs +++ b/preview/partials/head.ejs @@ -3,6 +3,5 @@ Postmark template preview - \ No newline at end of file diff --git a/preview/templateText.ejs b/preview/templateText.ejs index c7e1468..ca093dd 100644 --- a/preview/templateText.ejs +++ b/preview/templateText.ejs @@ -1,7 +1,6 @@ - From 087220ddfe3bebdf943e898ecbf23406e5f52081 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 12:22:40 -0700 Subject: [PATCH 18/37] Tidy up styles a little --- preview/assets/js/preview.js | 4 +- preview/assets/styles/styles.css | 87 ++++++++++++++++++-------------- preview/template.ejs | 4 +- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/preview/assets/js/preview.js b/preview/assets/js/preview.js index 9a3cfc4..889fd8a 100644 --- a/preview/assets/js/preview.js +++ b/preview/assets/js/preview.js @@ -18,7 +18,7 @@ mode.forEach(function(toggle) { // Hide all views document .querySelector('.js-' + modeToggle.dataset.mode) - .classList.add('hidden') + .classList.add('is-hidden') }) // Add active class @@ -28,7 +28,7 @@ mode.forEach(function(toggle) { // Show view document .querySelector('.js-' + this.dataset.mode) - .classList.remove('hidden') + .classList.remove('is-hidden') }) }) diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index e8d161a..5fcf269 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -23,12 +23,6 @@ body { background-color: var(--c-bg); } -.container { - margin: 0 auto; - padding: 56px 0; - max-width: 800px; -} - p, li { font-size: 16px; @@ -39,16 +33,6 @@ a { color: var(--c-text-primary); } -.section-title { - text-align: center; - color: var(--c-text-secondary); - font-size: 13px; - text-transform: uppercase; - font-weight: normal; - letter-spacing: 1px; - margin: 40px 0 20px; -} - /* *************************** */ /** Toolbars **/ @@ -134,7 +118,7 @@ a { margin-right: 8px; } - .toolbar .template-layout { + .toolbar_layout { align-self: center; margin-right: 5px; } @@ -201,6 +185,10 @@ a { } +/* *************************** */ +/* Template listing */ +/* *************************** */ + .template-list { list-style: none; padding: 0; @@ -295,6 +283,22 @@ a { /* Misc */ /* *************************** */ +.container { + margin: 0 auto; + padding: 56px 0; + max-width: 800px; +} + +.section-title { + text-align: center; + color: var(--c-text-secondary); + font-size: 13px; + text-transform: uppercase; + font-weight: normal; + letter-spacing: 1px; + margin: 40px 0 20px; +} + .subject { font-size: 13px; font-weight: normal; @@ -338,15 +342,15 @@ a { margin: 60px auto; max-width: 600px; } -.blank p { - margin-bottom: 0; -} -.blank .snippet { - text-align: left; -} -.blank .snippet pre { - white-space: pre-wrap; -} + .blank p { + margin-bottom: 0; + } + .blank .snippet { + text-align: left; + } + .blank .snippet pre { + white-space: pre-wrap; + } /* *************************** */ /* Instructions */ @@ -370,11 +374,12 @@ a { margin-top: .5em; } + /* *************************** */ /* Utilities */ /* *************************** */ -.hidden { +.is-hidden { display: none; } @@ -411,6 +416,10 @@ code { } +/* *************************** */ +/* Viewport icons */ +/* *************************** */ + .icon-view { width: 40px; padding: 0; @@ -454,16 +463,20 @@ code { } - .reloaded { - display: flex; - align-items: center; - font-size: 13px; - transition: 200ms all ease-in-out; - transform: translateY(4px); - opacity: 0; - font-weight: bold; - color: var(--c-primary); - } +/* *************************** */ +/* Reloaded message */ +/* *************************** */ + +.reloaded { + display: flex; + align-items: center; + font-size: 13px; + transition: 200ms all ease-in-out; + transform: translateY(4px); + opacity: 0; + font-weight: bold; + color: var(--c-primary); +} .reloaded::before { content: ''; diff --git a/preview/template.ejs b/preview/template.ejs index 551ea87..9cb1cca 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -12,7 +12,7 @@
    <% if (template.TemplateType === 'Standard') { %> - Layout: + Layout: <% if (template.LayoutTemplate) { %> <%- template.LayoutTemplate %> <% } else { %> @@ -44,7 +44,7 @@ <% } %> - + <%- include("partials/previewScripts.ejs") %> From 3b9601f9f95a4a5a5d4ad366a3923df02479640e Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 21 Nov 2019 13:19:51 -0700 Subject: [PATCH 19/37] Use button elements for toggles --- preview/assets/js/preview.js | 4 ---- preview/assets/styles/styles.css | 3 +++ preview/template.ejs | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/preview/assets/js/preview.js b/preview/assets/js/preview.js index 889fd8a..4582fe5 100644 --- a/preview/assets/js/preview.js +++ b/preview/assets/js/preview.js @@ -8,8 +8,6 @@ var currentMode = 'html' mode.forEach(function(toggle) { toggle.addEventListener('click', function(event) { - event.preventDefault() - // Reset all classes mode.forEach(function(modeToggle) { // Remove active class from all toggles @@ -41,8 +39,6 @@ var currentView = 'desktop' view.forEach(function(toggle) { // Add click event toggle.addEventListener('click', function(event) { - event.preventDefault() - view.forEach(function(viewToggle) { // Remove active class from all toggles viewToggle.classList.remove(activeToggleClass) diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index 5fcf269..70da3b7 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -166,6 +166,9 @@ a { font-weight: bold; transition: 100ms all ease-in-out; align-items: center; + outline: none; + cursor: pointer; + border: none; } .toggle_item:last-child { diff --git a/preview/template.ejs b/preview/template.ejs index 9cb1cca..9d6be6b 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -22,13 +22,13 @@ <% } %>
    - - + +
    - HTML - Text + +
    From d1f4a8e6be6672eefb8a0aae35fc33ebd61adedc Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Mon, 2 Dec 2019 10:34:43 -0700 Subject: [PATCH 20/37] Optimize for smaller screens --- preview/assets/styles/styles.css | 97 +++++++++++++++++++------------- preview/index.ejs | 17 +++--- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index 70da3b7..373a5c5 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -81,6 +81,7 @@ a { } .toolbar_back { + display: none; text-decoration: none; border: 2px solid #dbdaeb; margin: 2px 0; @@ -96,22 +97,33 @@ a { border-color: var(--c-primary); } + @media only screen and (min-width: 610px) { + .toolbar_back { + display: inline-block; + } + } + .toolbar_path { - display: flex; - font-size: 12px; - color: var(--c-text-secondary); - align-self: center; + display: none; } - .toolbar_path::before { - display: inline-block; - content: ''; - background-image: url(../images/folder.svg); - background-repeat: no-repeat; - background-size: 100%; - width: 15px; - height: 12px; - margin-right: 5px; + @media only screen and (min-width: 610px) { + .toolbar_path { + display: flex; + font-size: 12px; + color: var(--c-text-secondary); + align-self: center; + } + .toolbar_path::before { + display: inline-block; + content: ''; + background-image: url(../images/folder.svg); + background-repeat: no-repeat; + background-size: 100%; + width: 15px; + height: 12px; + margin-right: 5px; + } } .toolbar_icon { @@ -123,6 +135,7 @@ a { margin-right: 5px; } + .sub-toolbar { display: flex; justify-content: space-between; @@ -159,6 +172,7 @@ a { box-sizing: border-box; text-decoration: none; color: var(--c-text-secondary); + background-color: transparent; padding: 0 12px; font-size: 13px; margin: 3px; @@ -228,9 +242,16 @@ a { .template-layout, .template-layout a { + display: none; color: var(--c-text-secondary); font-size: 12px; } +@media only screen and (min-width: 610px) { + .template-layout, + .template-layout a { + display: inline-block; + } +} /* *************************** */ @@ -341,39 +362,30 @@ a { /* *************************** */ .blank { - text-align: center; margin: 60px auto; - max-width: 600px; + padding: 0 15px; } +@media only screen and (min-width: 610px) { + .blank { + max-width: 600px; + padding: 0; + } +} + .blank p { margin-bottom: 0; } - .blank .snippet { - text-align: left; - } - .blank .snippet pre { - white-space: pre-wrap; - } - -/* *************************** */ -/* Instructions */ -/* *************************** */ - -.instructions { - margin: 0 auto; - max-width: 600px; -} - .instructions h4 { - text-align: center; + .blank h4 { + margin-top: 2em; margin-bottom: 1.5em; } - .instructions ol li { + .blank ol li { margin-bottom: 1.5em; } - .instructions ul li { + .blank ul li { margin-bottom: .5em; } - .instructions ul { + .blank ul { margin-top: .5em; } @@ -410,12 +422,19 @@ code { border-radius: 4px; font-size: 13px; border: 1px solid rgba(0,0,0,.1); + line-height: 1.4; } + .snippet pre { + margin: 0; + word-break: break-all; + white-space: pre-wrap; + } + .snippet--fit { + display: inline-block; + } -.snippet pre { - margin: 0; - word-wrap: break-word; - word-break: break-all; +.center { + text-align: center; } diff --git a/preview/index.ejs b/preview/index.ejs index da60b7d..42c62a3 100644 --- a/preview/index.ejs +++ b/preview/index.ejs @@ -42,20 +42,19 @@ <% } else { %>
    - -

    No templates were found

    -

    Pull your templates from Postmark:

    -
    postmark templates pull <%- path %>
    -
    +
    + +

    No templates were found

    +

    Pull your templates from Postmark:

    +
    postmark templates pull <%- path %>
    +
    -
    -

    Or build your templates locally

    +

    Or build your templates locally

    1. Create a new folder for your template:
       cd <%- path %>
      -mkdir password-reset
      -
      +mkdir password-reset
  • Your template folder should contain the following files:
      From 88317a559f068568eb9e00a91581d7371dd2c15c Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Mon, 2 Dec 2019 12:50:37 -0700 Subject: [PATCH 21/37] Use template validation endpoint Instead of offline compilations using regex --- src/commands/templates/helpers.ts | 44 -------------- src/commands/templates/preview.ts | 96 +++++++++++++++++++++---------- src/types/Template.ts | 1 + 3 files changed, 66 insertions(+), 75 deletions(-) diff --git a/src/commands/templates/helpers.ts b/src/commands/templates/helpers.ts index 2f4da70..5355499 100644 --- a/src/commands/templates/helpers.ts +++ b/src/commands/templates/helpers.ts @@ -1,6 +1,5 @@ import { join, dirname } from 'path' import { readJsonSync, readFileSync, existsSync } from 'fs-extra' -import { filter, find, replace } from 'lodash' import traverse from 'traverse' import dirTree from 'directory-tree' import { TemplateManifest, MetaFileTraverse, MetaFile } from '../../types' @@ -64,46 +63,3 @@ export const createManifestItem = (file: any): MetaFile | null => { return null } - -/** - * Combine all templates and layouts - */ -export const compileTemplates = (manifest: TemplateManifest[]) => { - let compiled: any = [] - const layouts = filter(manifest, { TemplateType: 'Layout' }) - const templates = filter(manifest, { TemplateType: 'Standard' }) - - templates.forEach(template => { - const layout = find(layouts, { Alias: template.LayoutTemplate || '' }) - const html = template.HtmlBody || '' - const text = template.TextBody || '' - - compiled.push({ - ...template, - HtmlBody: - layout && layout.HtmlBody - ? compileTemplate(layout.HtmlBody, html) - : html, - TextBody: - layout && layout.TextBody - ? compileTemplate(layout.TextBody, text) - : text, - }) - }) - - layouts.forEach(layout => { - if (!layout.HtmlBody && !layout.TextBody) return - const content = 'Template content is inserted here.' - - compiled.push({ - ...layout, - HtmlBody: compileTemplate(layout.HtmlBody || '', content), - TextBody: compileTemplate(layout.TextBody || '', content), - }) - }) - - return compiled -} - -export const compileTemplate = (layout: string, template: string): string => - replace(layout, /({{{)(.?@content.?)(}}})/g, template) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index c27f6d1..cf04865 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -5,13 +5,18 @@ import untildify from 'untildify' import express from 'express' import { createMonitor } from 'watch' import consolidate from 'consolidate' -import { createManifest, compileTemplates } from './helpers' +import { ServerClient } from 'postmark' +import { createManifest } from './helpers' import { TemplatePreviewArguments } from '../../types' -import { log } from '../../utils' +import { log, validateToken } from '../../utils' export const command = 'preview [options]' export const desc = 'Preview your templates and layouts together' export const builder = { + 'server-token': { + type: 'string', + hidden: true, + }, port: { type: 'number', describe: 'The port to open up the preview server on', @@ -24,10 +29,17 @@ export const handler = (args: TemplatePreviewArguments) => exec(args) /** * Execute the command */ -const exec = (args: TemplatePreviewArguments): void => validateDirectory(args) +const exec = (args: TemplatePreviewArguments) => { + const { serverToken } = args + + return validateToken(serverToken).then(() => { + validateDirectory(args) + }) +} const validateDirectory = (args: TemplatePreviewArguments) => { - const rootPath: string = untildify(args.templatesdirectory) + const { templatesdirectory } = args + const rootPath: string = untildify(templatesdirectory) // Check if path exists if (!existsSync(rootPath)) { @@ -42,15 +54,17 @@ const validateDirectory = (args: TemplatePreviewArguments) => { * Preview */ const preview = (args: TemplatePreviewArguments) => { - const { port, templatesdirectory } = args - console.log(`${title} Starting template preview server...`) + const { port, templatesdirectory, serverToken } = args + log(`${title} Starting template preview server...`) + + // Start server const app = express() const server = require('http').createServer(app) const io = require('socket.io')(server) - // Cache manifest and compiled templates + // Cache manifest and Postmark server + const client = new ServerClient(serverToken) let manifest = createManifest(templatesdirectory) - let compiled = compileTemplates(manifest) // Static assets app.use(express.static('preview/assets')) @@ -58,12 +72,10 @@ const preview = (args: TemplatePreviewArguments) => { // Update manifest when files change createMonitor(untildify(templatesdirectory), { interval: 2 }, monitor => { function eventHandler() { - // Update manifest and compiled templates manifest = createManifest(templatesdirectory) - compiled = compileTemplates(manifest) - console.log(`${title} File changed. Reloading browser...`) // Trigger reload on client + log(`${title} File changed. Reloading browser...`) io.emit('change') } @@ -112,43 +124,65 @@ const preview = (args: TemplatePreviewArguments) => { * Get template HTML version by alias */ app.get('/html/:alias', (req, res) => { - const compiled = compileTemplates(manifest) - const template: any = find(compiled, { Alias: req.params.alias }) + const template: any = find(manifest, { Alias: req.params.alias }) if (template && template.HtmlBody) { - return res.send(template.HtmlBody) + client + .validateTemplate({ + Subject: template.Subject, + HtmlBody: template.HtmlBody, + TextBody: template.TextBody || '', + LayoutTemplate: template.LayoutTemplate || '', + }) + .then(result => { + return res.send(result.HtmlBody.RenderedContent) + }) + .catch(error => { + return res.send(500).send(error) + }) + } else { + return res.status(404).send('Not found!') } - - return res.status(404).send('Not found!') }) /** * Get template text version by alias */ app.get('/text/:alias', (req, res) => { - const template: any = find(compiled, { Alias: req.params.alias }) + const template: any = find(manifest, { Alias: req.params.alias }) if (template && template.TextBody) { - consolidate.ejs( - 'preview/templateText.ejs', - { body: template.TextBody }, - (err, html) => { - if (err) return res.send(err) - - return res.send(html) - } - ) + client + .validateTemplate({ + Subject: template.Subject, + HtmlBody: template.HtmlBody, + TextBody: template.TextBody || '', + LayoutTemplate: template.LayoutTemplate || '', + }) + .then(result => { + consolidate.ejs( + 'preview/templateText.ejs', + { body: result.TextBody.RenderedContent }, + (err, html) => { + if (err) return res.send(err) + + return res.send(html) + } + ) + }) + .catch(error => { + return res.send(500).send(error) + }) } else { return res.status(404).send('Not found!') } }) server.listen(port, () => { - console.log(`${title} Template preview server ready. Happy coding!`) - - console.log(divider) - console.log(`URL: ${chalk.green(`http://localhost:${port}`)}`) - console.log(divider) + log(`${title} Template preview server ready. Happy coding!`) + log(divider) + log(`URL: ${chalk.green(`http://localhost:${port}`)}`) + log(divider) }) } diff --git a/src/types/Template.ts b/src/types/Template.ts index 75917d8..bba4e9d 100644 --- a/src/types/Template.ts +++ b/src/types/Template.ts @@ -70,6 +70,7 @@ export interface TemplatePushArguments { } export interface TemplatePreviewArguments { + serverToken: string templatesdirectory: string port: number } From 883215f1710f4e07b6c2326c76796a25b0c7c9d1 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Mon, 2 Dec 2019 15:33:52 -0700 Subject: [PATCH 22/37] Show loading indicator while preview is loading --- preview/assets/styles/styles.css | 93 ++++++++++++++++++++++++++++---- preview/template.ejs | 10 ++++ 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index 373a5c5..a12d7ae 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -519,15 +519,90 @@ code { animation: 280ms 500ms 1 linear spinzoom; } - @keyframes spinzoom { - 0% { - transform: rotate(0deg) scale(1); + +/* *************************** */ +/* Loading indicator */ +/* *************************** */ + + .loader { + position: absolute; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); + text-align: center; + } + .loader_spinner { + display: inline-block; + position: relative; + width: 30px; + height: 30px; + border-radius: 50%; + background: linear-gradient(var(--c-text-secondary), var(--c-primary), var(--c-postmark)); + animation: spin 600ms linear infinite; } - 50% { - transform: rotate(220deg) scale(1.6); - filter: blur(.5px); + .loader_spinner span { + position: absolute; + width: 100%; + height: 100%; + border-radius: 50%; + left: 0; + background: linear-gradient(var(--c-text-secondary), var(--c-primary), var(--c-postmark)); } - 100% { - transform: rotate(359deg) scale(1); + .loader_spinner span:nth-child(1) { + filter: blur(2px); + } + .loader_spinner span:nth-child(2) { + filter: blur(4px); + } + .loader_spinner span:nth-child(3) { + filter: blur(10px); + } + .loader_spinner span:nth-child(4) { + filter: blur(15px); + } + .loader_spinner::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + right: 3px; + bottom: 3px; + background: #f1f1f1; + border: solid rgba(255, 255, 255, .7) 3px; + border-radius: 50%; + } + .loader_text { + color: var(--c-text-secondary); + font-weight: bold; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 1px; + margin: 1em 0 0; } - } \ No newline at end of file + + +/* *************************** */ +/* Animations */ +/* *************************** */ + +@keyframes spinzoom { + 0% { + transform: rotate(0deg) scale(1); + } + 50% { + transform: rotate(220deg) scale(1.6); + filter: blur(.5px); + } + 100% { + transform: rotate(359deg) scale(1); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/preview/template.ejs b/preview/template.ejs index 9d6be6b..dd4d92c 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -43,6 +43,16 @@
  • <% } %> +
    +
    + + + + +
    +

    Fetching preview...

    +
    + From 5220d12bb2fc17231f2220fa0dfa9314c0da0ba6 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Mon, 2 Dec 2019 16:23:16 -0700 Subject: [PATCH 23/37] HTML and Text version 404 pages --- preview/assets/styles/styles.css | 23 +++++++++++++++++++++++ preview/template404.ejs | 11 +++++++++++ src/commands/templates/preview.ts | 28 +++++++++++++++++++++------- 3 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 preview/template404.ejs diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index a12d7ae..c9e31c8 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -390,6 +390,29 @@ a { } +/* *************************** */ +/* Missing template */ +/* *************************** */ + +.missing-body { + background-color: #FFF; +} + +.missing-template { + margin: 0; + position: absolute; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); + text-align: center; +} + .missing-template h2 { + color: var(--c-text-secondary); + font-weight: normal; + font-size: 16px; + } + + /* *************************** */ /* Utilities */ /* *************************** */ diff --git a/preview/template404.ejs b/preview/template404.ejs new file mode 100644 index 0000000..b661e59 --- /dev/null +++ b/preview/template404.ejs @@ -0,0 +1,11 @@ + + + + + + +
    +

    No <%- version %> version

    +
    + + diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index cf04865..4c2b0b4 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -129,9 +129,8 @@ const preview = (args: TemplatePreviewArguments) => { if (template && template.HtmlBody) { client .validateTemplate({ - Subject: template.Subject, + Subject: template.Subject || '', HtmlBody: template.HtmlBody, - TextBody: template.TextBody || '', LayoutTemplate: template.LayoutTemplate || '', }) .then(result => { @@ -141,7 +140,15 @@ const preview = (args: TemplatePreviewArguments) => { return res.send(500).send(error) }) } else { - return res.status(404).send('Not found!') + consolidate.ejs( + 'preview/template404.ejs', + { version: 'HTML' }, + (err, html) => { + if (err) return res.send(err) + + return res.status(404).send(html) + } + ) } }) @@ -154,9 +161,8 @@ const preview = (args: TemplatePreviewArguments) => { if (template && template.TextBody) { client .validateTemplate({ - Subject: template.Subject, - HtmlBody: template.HtmlBody, - TextBody: template.TextBody || '', + Subject: template.Subject || '', + TextBody: template.TextBody, LayoutTemplate: template.LayoutTemplate || '', }) .then(result => { @@ -174,7 +180,15 @@ const preview = (args: TemplatePreviewArguments) => { return res.send(500).send(error) }) } else { - return res.status(404).send('Not found!') + consolidate.ejs( + 'preview/template404.ejs', + { version: 'Text' }, + (err, html) => { + if (err) return res.send(err) + + return res.status(404).send(html) + } + ) } }) From 0a155027ccf395683327dcf19abecdb4edd7c30d Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Tue, 3 Dec 2019 08:56:30 -0700 Subject: [PATCH 24/37] Render layouts --- src/commands/templates/preview.ts | 49 +++++++++++++------------------ 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index 4c2b0b4..527b6c7 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -125,14 +125,15 @@ const preview = (args: TemplatePreviewArguments) => { */ app.get('/html/:alias', (req, res) => { const template: any = find(manifest, { Alias: req.params.alias }) + const payload = { + HtmlBody: template.HtmlBody, + LayoutTemplate: template.LayoutTemplate || '', + TemplateType: template.TemplateType, + } if (template && template.HtmlBody) { client - .validateTemplate({ - Subject: template.Subject || '', - HtmlBody: template.HtmlBody, - LayoutTemplate: template.LayoutTemplate || '', - }) + .validateTemplate(payload) .then(result => { return res.send(result.HtmlBody.RenderedContent) }) @@ -140,15 +141,7 @@ const preview = (args: TemplatePreviewArguments) => { return res.send(500).send(error) }) } else { - consolidate.ejs( - 'preview/template404.ejs', - { version: 'HTML' }, - (err, html) => { - if (err) return res.send(err) - - return res.status(404).send(html) - } - ) + renderTemplate404(res, 'HTML') } }) @@ -157,14 +150,15 @@ const preview = (args: TemplatePreviewArguments) => { */ app.get('/text/:alias', (req, res) => { const template: any = find(manifest, { Alias: req.params.alias }) + const payload = { + TextBody: template.TextBody, + LayoutTemplate: template.LayoutTemplate || '', + TemplateType: template.TemplateType, + } if (template && template.TextBody) { client - .validateTemplate({ - Subject: template.Subject || '', - TextBody: template.TextBody, - LayoutTemplate: template.LayoutTemplate || '', - }) + .validateTemplate(payload) .then(result => { consolidate.ejs( 'preview/templateText.ejs', @@ -180,15 +174,7 @@ const preview = (args: TemplatePreviewArguments) => { return res.send(500).send(error) }) } else { - consolidate.ejs( - 'preview/template404.ejs', - { version: 'Text' }, - (err, html) => { - if (err) return res.send(err) - - return res.status(404).send(html) - } - ) + renderTemplate404(res, 'Text') } }) @@ -202,3 +188,10 @@ const preview = (args: TemplatePreviewArguments) => { const title = `${chalk.yellow('ミ▢ Postmark')}${chalk.gray(':')}` const divider = chalk.gray('-'.repeat(34)) + +const renderTemplate404 = (res: any, version: string) => + consolidate.ejs('preview/template404.ejs', { version }, (err, html) => { + if (err) return res.send(err) + + return res.status(404).send(html) + }) From c11898afdf822a8517f9e57f3af82d9075206173 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Tue, 3 Dec 2019 08:56:44 -0700 Subject: [PATCH 25/37] Tweak loader styles --- preview/assets/styles/styles.css | 17 ++++------------- preview/template.ejs | 3 --- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index c9e31c8..bb6aa48 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -557,8 +557,8 @@ code { .loader_spinner { display: inline-block; position: relative; - width: 30px; - height: 30px; + width: 25px; + height: 25px; border-radius: 50%; background: linear-gradient(var(--c-text-secondary), var(--c-primary), var(--c-postmark)); animation: spin 600ms linear infinite; @@ -572,16 +572,7 @@ code { background: linear-gradient(var(--c-text-secondary), var(--c-primary), var(--c-postmark)); } .loader_spinner span:nth-child(1) { - filter: blur(2px); - } - .loader_spinner span:nth-child(2) { - filter: blur(4px); - } - .loader_spinner span:nth-child(3) { - filter: blur(10px); - } - .loader_spinner span:nth-child(4) { - filter: blur(15px); + filter: blur(5px); } .loader_spinner::after { content: ''; @@ -591,7 +582,7 @@ code { right: 3px; bottom: 3px; background: #f1f1f1; - border: solid rgba(255, 255, 255, .7) 3px; + border: solid rgba(255, 255, 255, .4) 3px; border-radius: 50%; } .loader_text { diff --git a/preview/template.ejs b/preview/template.ejs index dd4d92c..4bdd539 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -46,9 +46,6 @@
    - - -

    Fetching preview...

    From b989b7c81c033894bd31ee173184b0433cb5515f Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Tue, 3 Dec 2019 10:20:57 -0700 Subject: [PATCH 26/37] Use local layout instead of layout on server --- src/commands/templates/preview.ts | 37 +++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index 527b6c7..4806d4f 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -125,20 +125,25 @@ const preview = (args: TemplatePreviewArguments) => { */ app.get('/html/:alias', (req, res) => { const template: any = find(manifest, { Alias: req.params.alias }) - const payload = { - HtmlBody: template.HtmlBody, - LayoutTemplate: template.LayoutTemplate || '', - TemplateType: template.TemplateType, - } + const layout: any = find(manifest, { Alias: template.LayoutTemplate }) if (template && template.HtmlBody) { + const source = layout + ? combineTemplate(layout.HtmlBody, template.HtmlBody) + : template.HtmlBody + + const payload = { + HtmlBody: source, + TemplateType: template.TemplateType, + } + client .validateTemplate(payload) .then(result => { return res.send(result.HtmlBody.RenderedContent) }) .catch(error => { - return res.send(500).send(error) + return res.status(500).send(error) }) } else { renderTemplate404(res, 'HTML') @@ -150,13 +155,18 @@ const preview = (args: TemplatePreviewArguments) => { */ app.get('/text/:alias', (req, res) => { const template: any = find(manifest, { Alias: req.params.alias }) - const payload = { - TextBody: template.TextBody, - LayoutTemplate: template.LayoutTemplate || '', - TemplateType: template.TemplateType, - } + const layout: any = find(manifest, { Alias: template.LayoutTemplate }) if (template && template.TextBody) { + const source = layout + ? combineTemplate(layout.TextBody, template.TextBody) + : template.TextBody + + const payload = { + TextBody: source, + TemplateType: template.TemplateType, + } + client .validateTemplate(payload) .then(result => { @@ -171,7 +181,7 @@ const preview = (args: TemplatePreviewArguments) => { ) }) .catch(error => { - return res.send(500).send(error) + return res.status(500).send(error) }) } else { renderTemplate404(res, 'Text') @@ -189,6 +199,9 @@ const preview = (args: TemplatePreviewArguments) => { const title = `${chalk.yellow('ミ▢ Postmark')}${chalk.gray(':')}` const divider = chalk.gray('-'.repeat(34)) +const combineTemplate = (layout: string, template: string): string => + replace(layout, /({{{)(.?@content.?)(}}})/g, template) + const renderTemplate404 = (res: any, version: string) => consolidate.ejs('preview/template404.ejs', { version }, (err, html) => { if (err) return res.send(err) From cd00afcd645347bb94b2eb915106ac72c2651b7f Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Tue, 3 Dec 2019 11:09:50 -0700 Subject: [PATCH 27/37] Debounce file change events --- src/commands/templates/preview.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index 4806d4f..f2e8b06 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -1,6 +1,6 @@ import chalk from 'chalk' import { existsSync } from 'fs-extra' -import { filter, find } from 'lodash' +import { filter, find, replace, debounce } from 'lodash' import untildify from 'untildify' import express from 'express' import { createMonitor } from 'watch' @@ -69,20 +69,20 @@ const preview = (args: TemplatePreviewArguments) => { // Static assets app.use(express.static('preview/assets')) - // Update manifest when files change - createMonitor(untildify(templatesdirectory), { interval: 2 }, monitor => { - function eventHandler() { - manifest = createManifest(templatesdirectory) + const updateEvent = () => { + // Generate new manifest + manifest = createManifest(templatesdirectory) - // Trigger reload on client - log(`${title} File changed. Reloading browser...`) - io.emit('change') - } + // Trigger reload on client + log(`${title} File changed. Reloading browser...`) + io.emit('change') + } - // Monitor all events - monitor.on('created', eventHandler) - monitor.on('changed', eventHandler) - monitor.on('removed', eventHandler) + // Watch for file changes + createMonitor(untildify(templatesdirectory), { interval: 2 }, monitor => { + monitor.on('created', debounce(updateEvent, 1000)) + monitor.on('changed', debounce(updateEvent, 1000)) + monitor.on('removed', debounce(updateEvent, 1000)) }) // Template listing From ea35aa5b7d851ad5a8cca1e9b30933321132b287 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Tue, 3 Dec 2019 11:58:10 -0700 Subject: [PATCH 28/37] Show template validation errors --- preview/assets/styles/styles.css | 21 ++++++++++++---- preview/template404.ejs | 4 ++-- preview/templateInvalid.ejs | 16 +++++++++++++ src/commands/templates/preview.ts | 40 +++++++++++++++++++++---------- 4 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 preview/templateInvalid.ejs diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index bb6aa48..e815f78 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -394,22 +394,33 @@ a { /* Missing template */ /* *************************** */ -.missing-body { +.template-error-body { background-color: #FFF; } -.missing-template { +.template-error { margin: 0; position: absolute; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); - text-align: center; } - .missing-template h2 { + .template-error h1, + .template-error h2 { color: var(--c-text-secondary); - font-weight: normal; font-size: 16px; + text-align: center; + } + .template-error h2 { + font-weight: normal; + } + .template-error_list { + text-align: left; + } + .template-error_list li { + color: var(--c-text-secondary); + font-size: 14px; + margin-bottom: .5em; } diff --git a/preview/template404.ejs b/preview/template404.ejs index b661e59..428b82d 100644 --- a/preview/template404.ejs +++ b/preview/template404.ejs @@ -3,8 +3,8 @@ - -
    + +

    No <%- version %> version

    diff --git a/preview/templateInvalid.ejs b/preview/templateInvalid.ejs new file mode 100644 index 0000000..3b002a4 --- /dev/null +++ b/preview/templateInvalid.ejs @@ -0,0 +1,16 @@ + + + + + + +
    +

    There’s an issue with your template

    +
      + <% errors.forEach(function(error) { %> +
    • <%- error.Message %>
    • + <% }) %> +
    +
    + + diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index f2e8b06..e6a7b80 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -140,13 +140,17 @@ const preview = (args: TemplatePreviewArguments) => { client .validateTemplate(payload) .then(result => { - return res.send(result.HtmlBody.RenderedContent) + if (result.HtmlBody.ContentIsValid) { + return res.send(result.HtmlBody.RenderedContent) + } + + return renderTemplateInvalid(res, result.HtmlBody.ValidationErrors) }) .catch(error => { return res.status(500).send(error) }) } else { - renderTemplate404(res, 'HTML') + return renderTemplate404(res, 'HTML') } }) @@ -170,21 +174,17 @@ const preview = (args: TemplatePreviewArguments) => { client .validateTemplate(payload) .then(result => { - consolidate.ejs( - 'preview/templateText.ejs', - { body: result.TextBody.RenderedContent }, - (err, html) => { - if (err) return res.send(err) - - return res.send(html) - } - ) + if (result.TextBody.ContentIsValid) { + return renderTemplateText(res, result.TextBody.RenderedContent) + } + + return renderTemplateInvalid(res, result.TextBody.ValidationErrors) }) .catch(error => { return res.status(500).send(error) }) } else { - renderTemplate404(res, 'Text') + return renderTemplate404(res, 'Text') } }) @@ -202,6 +202,22 @@ const divider = chalk.gray('-'.repeat(34)) const combineTemplate = (layout: string, template: string): string => replace(layout, /({{{)(.?@content.?)(}}})/g, template) +/* Render Templates */ + +const renderTemplateText = (res: any, body: string) => + consolidate.ejs('preview/templateText.ejs', { body }, (err, html) => { + if (err) return res.send(err) + + return res.send(html) + }) + +const renderTemplateInvalid = (res: any, errors: any) => + consolidate.ejs('preview/templateInvalid.ejs', { errors }, (err, html) => { + if (err) return res.send(err) + + return res.send(html) + }) + const renderTemplate404 = (res: any, version: string) => consolidate.ejs('preview/template404.ejs', { version }, (err, html) => { if (err) return res.send(err) From d554447a492134c10c45e9d4513c1d00be36b2b6 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Tue, 3 Dec 2019 12:34:31 -0700 Subject: [PATCH 29/37] Tweak types --- src/commands/templates/preview.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index e6a7b80..a1de66e 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -106,7 +106,7 @@ const preview = (args: TemplatePreviewArguments) => { * Get template by alias */ app.get('/:alias', (req, res) => { - const template: any = find(manifest, { Alias: req.params.alias }) + const template = find(manifest, { Alias: req.params.alias }) if (template) { consolidate.ejs('preview/template.ejs', { template }, (err, html) => { @@ -204,21 +204,21 @@ const combineTemplate = (layout: string, template: string): string => /* Render Templates */ -const renderTemplateText = (res: any, body: string) => +const renderTemplateText = (res: express.Response, body: string) => consolidate.ejs('preview/templateText.ejs', { body }, (err, html) => { if (err) return res.send(err) return res.send(html) }) -const renderTemplateInvalid = (res: any, errors: any) => +const renderTemplateInvalid = (res: express.Response, errors: any) => consolidate.ejs('preview/templateInvalid.ejs', { errors }, (err, html) => { if (err) return res.send(err) return res.send(html) }) -const renderTemplate404 = (res: any, version: string) => +const renderTemplate404 = (res: express.Response, version: string) => consolidate.ejs('preview/template404.ejs', { version }, (err, html) => { if (err) return res.send(err) From de90bbfbe730709655206aea01c4de885334a213 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Tue, 3 Dec 2019 14:48:25 -0700 Subject: [PATCH 30/37] Tweak template error container --- preview/assets/styles/styles.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index e815f78..f6399d0 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -404,7 +404,13 @@ a { left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); + width: 90%; } + @media only screen and (min-width: 610px) { + .template-error { + width: 500px; + } + } .template-error h1, .template-error h2 { color: var(--c-text-secondary); From 33d03ea166e4526df671fd4bfdd5619b743488f3 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 4 Dec 2019 09:09:51 -0700 Subject: [PATCH 31/37] Fix a few bugs --- preview/assets/js/preview.js | 9 +++++++++ preview/assets/styles/styles.css | 5 +++++ preview/template.ejs | 2 +- src/commands/templates/preview.ts | 16 +++++++++------- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/preview/assets/js/preview.js b/preview/assets/js/preview.js index 4582fe5..4fe2bd6 100644 --- a/preview/assets/js/preview.js +++ b/preview/assets/js/preview.js @@ -76,3 +76,12 @@ function keypress(e) { } document.onkeydown = keypress + +// Manage state when HTML iframe is finished loading +document.querySelector('.js-html').onload = function() { + // Hide loading indicator + document.querySelector('.js-loader').classList.add('is-hidden') + + // Add state class to HTML iframe + this.classList.add('is-loaded') +} diff --git a/preview/assets/styles/styles.css b/preview/assets/styles/styles.css index f6399d0..5815373 100644 --- a/preview/assets/styles/styles.css +++ b/preview/assets/styles/styles.css @@ -287,6 +287,10 @@ a { width: 360px; } + .preview-iframe.is-loaded { + background-color: #FFF; + } + .preview-text { padding: 50px; background-color: #FFF; @@ -570,6 +574,7 @@ code { top: 50%; transform: translateX(-50%) translateY(-50%); text-align: center; + z-index: 2; } .loader_spinner { display: inline-block; diff --git a/preview/template.ejs b/preview/template.ejs index 4bdd539..cc7b1d8 100644 --- a/preview/template.ejs +++ b/preview/template.ejs @@ -43,7 +43,7 @@
    <% } %> -
    +
    diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index a1de66e..750abfb 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -116,7 +116,7 @@ const preview = (args: TemplatePreviewArguments) => { }) } else { // Redirect to index - res.redirect(301, '/') + return res.redirect(301, '/') } }) @@ -128,9 +128,10 @@ const preview = (args: TemplatePreviewArguments) => { const layout: any = find(manifest, { Alias: template.LayoutTemplate }) if (template && template.HtmlBody) { - const source = layout - ? combineTemplate(layout.HtmlBody, template.HtmlBody) - : template.HtmlBody + const source = + layout && layout.HtmlBody + ? combineTemplate(layout.HtmlBody, template.HtmlBody) + : template.HtmlBody const payload = { HtmlBody: source, @@ -162,9 +163,10 @@ const preview = (args: TemplatePreviewArguments) => { const layout: any = find(manifest, { Alias: template.LayoutTemplate }) if (template && template.TextBody) { - const source = layout - ? combineTemplate(layout.TextBody, template.TextBody) - : template.TextBody + const source = + layout && layout.TextBody + ? combineTemplate(layout.TextBody, template.TextBody) + : template.TextBody const payload = { TextBody: source, From 4ca556bc9c11b70e941d411d5be9146b0f724426 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 4 Dec 2019 09:32:31 -0700 Subject: [PATCH 32/37] Generate manifest when loading the index page --- src/commands/templates/preview.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index 750abfb..0c4a9ae 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -87,6 +87,7 @@ const preview = (args: TemplatePreviewArguments) => { // Template listing app.get('/', (req, res) => { + manifest = createManifest(templatesdirectory) const templates = filter(manifest, { TemplateType: 'Standard' }) const layouts = filter(manifest, { TemplateType: 'Layout' }) const path = untildify(templatesdirectory).replace(/\/$/, '') From c82a5e7a8de07af587a021e81615041eca2eb698 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 4 Dec 2019 09:52:11 -0700 Subject: [PATCH 33/37] Handle server token prompt scenario --- src/commands/templates/preview.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index 0c4a9ae..1efea52 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -32,12 +32,15 @@ export const handler = (args: TemplatePreviewArguments) => exec(args) const exec = (args: TemplatePreviewArguments) => { const { serverToken } = args - return validateToken(serverToken).then(() => { - validateDirectory(args) + return validateToken(serverToken).then(token => { + validateDirectory(token, args) }) } -const validateDirectory = (args: TemplatePreviewArguments) => { +const validateDirectory = ( + serverToken: string, + args: TemplatePreviewArguments +) => { const { templatesdirectory } = args const rootPath: string = untildify(templatesdirectory) @@ -47,14 +50,14 @@ const validateDirectory = (args: TemplatePreviewArguments) => { return process.exit(1) } - return preview(args) + return preview(serverToken, args) } /** * Preview */ -const preview = (args: TemplatePreviewArguments) => { - const { port, templatesdirectory, serverToken } = args +const preview = (serverToken: string, args: TemplatePreviewArguments) => { + const { port, templatesdirectory } = args log(`${title} Starting template preview server...`) // Start server From db3a362c8eed6e3f1b8e030dc7c16cb26af676e9 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 4 Dec 2019 12:11:00 -0700 Subject: [PATCH 34/37] Render error if layout is missing content --- src/commands/templates/preview.ts | 143 +++++++++++++++++------------- 1 file changed, 82 insertions(+), 61 deletions(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index 1efea52..dacfc10 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -98,11 +98,7 @@ const preview = (serverToken: string, args: TemplatePreviewArguments) => { consolidate.ejs( 'preview/index.ejs', { templates, layouts, path }, - (err, html) => { - if (err) return res.send(err) - - return res.send(html) - } + (err, html) => renderTemplateContents(res, err, html) ) }) @@ -113,11 +109,9 @@ const preview = (serverToken: string, args: TemplatePreviewArguments) => { const template = find(manifest, { Alias: req.params.alias }) if (template) { - consolidate.ejs('preview/template.ejs', { template }, (err, html) => { - if (err) return res.send(err) - - return res.send(html) - }) + consolidate.ejs('preview/template.ejs', { template }, (err, html) => + renderTemplateContents(res, err, html) + ) } else { // Redirect to index return res.redirect(301, '/') @@ -132,28 +126,16 @@ const preview = (serverToken: string, args: TemplatePreviewArguments) => { const layout: any = find(manifest, { Alias: template.LayoutTemplate }) if (template && template.HtmlBody) { - const source = - layout && layout.HtmlBody - ? combineTemplate(layout.HtmlBody, template.HtmlBody) - : template.HtmlBody + // Render error if layout is specified, but HtmlBody is empty + if (layout && !layout.HtmlBody) + return renderTemplateInvalid(res, layoutError) const payload = { - HtmlBody: source, + HtmlBody: getSource('html', template, layout), TemplateType: template.TemplateType, } - client - .validateTemplate(payload) - .then(result => { - if (result.HtmlBody.ContentIsValid) { - return res.send(result.HtmlBody.RenderedContent) - } - - return renderTemplateInvalid(res, result.HtmlBody.ValidationErrors) - }) - .catch(error => { - return res.status(500).send(error) - }) + return validateTemplateRequest('html', payload, res) } else { return renderTemplate404(res, 'HTML') } @@ -167,28 +149,16 @@ const preview = (serverToken: string, args: TemplatePreviewArguments) => { const layout: any = find(manifest, { Alias: template.LayoutTemplate }) if (template && template.TextBody) { - const source = - layout && layout.TextBody - ? combineTemplate(layout.TextBody, template.TextBody) - : template.TextBody + // Render error if layout is specified, but HtmlBody is empty + if (layout && !layout.TextBody) + return renderTemplateInvalid(res, layoutError) const payload = { - TextBody: source, + TextBody: getSource('text', template, layout), TemplateType: template.TemplateType, } - client - .validateTemplate(payload) - .then(result => { - if (result.TextBody.ContentIsValid) { - return renderTemplateText(res, result.TextBody.RenderedContent) - } - - return renderTemplateInvalid(res, result.TextBody.ValidationErrors) - }) - .catch(error => { - return res.status(500).send(error) - }) + return validateTemplateRequest('text', payload, res) } else { return renderTemplate404(res, 'Text') } @@ -200,33 +170,84 @@ const preview = (serverToken: string, args: TemplatePreviewArguments) => { log(`URL: ${chalk.green(`http://localhost:${port}`)}`) log(divider) }) -} -const title = `${chalk.yellow('ミ▢ Postmark')}${chalk.gray(':')}` -const divider = chalk.gray('-'.repeat(34)) + const validateTemplateRequest = ( + version: 'html' | 'text', + payload: any, + res: express.Response + ) => { + const versionKey = version === 'html' ? 'HtmlBody' : 'TextBody' + + // Make request to Postmark + client + .validateTemplate(payload) + .then(result => { + if (result[versionKey].ContentIsValid) { + const renderedContent = result[versionKey].RenderedContent + + // Render raw source if HTML + if (version === 'html') { + return res.send(renderedContent) + } else { + // Render specific EJS with text content + return renderTemplateText(res, renderedContent) + } + } + + return renderTemplateInvalid(res, result[versionKey].ValidationErrors) + }) + .catch(error => { + return res.status(500).send(error) + }) + } +} const combineTemplate = (layout: string, template: string): string => replace(layout, /({{{)(.?@content.?)(}}})/g, template) +/* Console helpers */ + +const title = `${chalk.yellow('ミ▢ Postmark')}${chalk.gray(':')}` +const divider = chalk.gray('-'.repeat(34)) + /* Render Templates */ -const renderTemplateText = (res: express.Response, body: string) => - consolidate.ejs('preview/templateText.ejs', { body }, (err, html) => { - if (err) return res.send(err) +const getSource = (version: 'html' | 'text', template: any, layout?: any) => { + const versionKey = version === 'html' ? 'HtmlBody' : 'TextBody' - return res.send(html) - }) + if (layout) return combineTemplate(layout[versionKey], template[versionKey]) -const renderTemplateInvalid = (res: express.Response, errors: any) => - consolidate.ejs('preview/templateInvalid.ejs', { errors }, (err, html) => { - if (err) return res.send(err) + return template[versionKey] +} - return res.send(html) - }) +const renderTemplateText = (res: express.Response, body: string) => + consolidate.ejs('preview/templateText.ejs', { body }, (err, html) => + renderTemplateContents(res, err, html) + ) + +const renderTemplateInvalid = (res: express.Response, errors: any) => + consolidate.ejs('preview/templateInvalid.ejs', { errors }, (err, html) => + renderTemplateContents(res, err, html) + ) const renderTemplate404 = (res: express.Response, version: string) => - consolidate.ejs('preview/template404.ejs', { version }, (err, html) => { - if (err) return res.send(err) + consolidate.ejs('preview/template404.ejs', { version }, (err, html) => + renderTemplateContents(res, err, html) + ) + +const renderTemplateContents = ( + res: express.Response, + err: any, + html: string +) => { + if (err) return res.send(err) - return res.status(404).send(html) - }) + return res.send(html) +} + +const layoutError = [ + { + Message: + 'A TemplateLayout is specified, but it is either empty or missing.', + }, +] From 84bd11c8c21a7f987a432298aae68527ec595daa Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 4 Dec 2019 12:50:16 -0700 Subject: [PATCH 35/37] Automatically open browser --- package-lock.json | 13 +++++++++++++ package.json | 1 + src/commands/templates/preview.ts | 9 +++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0450416..395ff8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1584,6 +1584,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-wsl": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", + "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2072,6 +2077,14 @@ "mimic-fn": "^1.0.0" } }, + "open": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.0.0.tgz", + "integrity": "sha512-K6EKzYqnwQzk+/dzJAQSBORub3xlBTxMz+ntpZpH/LyCa1o6KjXhuN+2npAaI9jaSmU3R1Q8NWf4KUWcyytGsQ==", + "requires": { + "is-wsl": "^2.1.0" + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", diff --git a/package.json b/package.json index 4df820c..2d967e9 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "fs-extra": "^7.0.1", "inquirer": "^6.2.1", "lodash": "^4.17.11", + "open": "^7.0.0", "ora": "^3.0.0", "postmark": "^2.2.7", "request": "^2.88.0", diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index dacfc10..d21cf36 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -6,12 +6,13 @@ import express from 'express' import { createMonitor } from 'watch' import consolidate from 'consolidate' import { ServerClient } from 'postmark' +import open from 'open' import { createManifest } from './helpers' import { TemplatePreviewArguments } from '../../types' import { log, validateToken } from '../../utils' export const command = 'preview [options]' -export const desc = 'Preview your templates and layouts together' +export const desc = 'Preview your templates and layouts' export const builder = { 'server-token': { type: 'string', @@ -165,10 +166,14 @@ const preview = (serverToken: string, args: TemplatePreviewArguments) => { }) server.listen(port, () => { + const url = `http://localhost:${port}` + log(`${title} Template preview server ready. Happy coding!`) log(divider) - log(`URL: ${chalk.green(`http://localhost:${port}`)}`) + log(`URL: ${chalk.green(url)}`) log(divider) + + open(url) }) const validateTemplateRequest = ( From d9befee7b4435f4e568d67b7b7c24f699980b572 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Wed, 4 Dec 2019 13:34:15 -0700 Subject: [PATCH 36/37] Add a couple types --- src/commands/templates/preview.ts | 3 ++- src/types/Template.ts | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index d21cf36..e1df991 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -9,6 +9,7 @@ import { ServerClient } from 'postmark' import open from 'open' import { createManifest } from './helpers' import { TemplatePreviewArguments } from '../../types' +import { TemplateValidationOptions } from 'postmark/dist/client/models' import { log, validateToken } from '../../utils' export const command = 'preview [options]' @@ -178,7 +179,7 @@ const preview = (serverToken: string, args: TemplatePreviewArguments) => { const validateTemplateRequest = ( version: 'html' | 'text', - payload: any, + payload: TemplateValidationOptions, res: express.Response ) => { const versionKey = version === 'html' ? 'HtmlBody' : 'TextBody' diff --git a/src/types/Template.ts b/src/types/Template.ts index bba4e9d..5c740c6 100644 --- a/src/types/Template.ts +++ b/src/types/Template.ts @@ -92,3 +92,8 @@ export interface MetaFileTraverse { extension: string type: string } + +export interface TemplateValidationPayload { + TextBody: string + TemplateType: 'Standard' | 'Layout' +} From ebc926e8d3c8aca0b7f25da70fbc33d93001bd49 Mon Sep 17 00:00:00 2001 From: Derek Rushforth Date: Thu, 5 Dec 2019 09:41:09 -0700 Subject: [PATCH 37/37] Remove open package --- package-lock.json | 13 ------------- package.json | 1 - src/commands/templates/preview.ts | 3 --- 3 files changed, 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 395ff8e..0450416 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1584,11 +1584,6 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==" - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2077,14 +2072,6 @@ "mimic-fn": "^1.0.0" } }, - "open": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/open/-/open-7.0.0.tgz", - "integrity": "sha512-K6EKzYqnwQzk+/dzJAQSBORub3xlBTxMz+ntpZpH/LyCa1o6KjXhuN+2npAaI9jaSmU3R1Q8NWf4KUWcyytGsQ==", - "requires": { - "is-wsl": "^2.1.0" - } - }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", diff --git a/package.json b/package.json index 2d967e9..4df820c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "fs-extra": "^7.0.1", "inquirer": "^6.2.1", "lodash": "^4.17.11", - "open": "^7.0.0", "ora": "^3.0.0", "postmark": "^2.2.7", "request": "^2.88.0", diff --git a/src/commands/templates/preview.ts b/src/commands/templates/preview.ts index e1df991..35bfd3d 100644 --- a/src/commands/templates/preview.ts +++ b/src/commands/templates/preview.ts @@ -6,7 +6,6 @@ import express from 'express' import { createMonitor } from 'watch' import consolidate from 'consolidate' import { ServerClient } from 'postmark' -import open from 'open' import { createManifest } from './helpers' import { TemplatePreviewArguments } from '../../types' import { TemplateValidationOptions } from 'postmark/dist/client/models' @@ -173,8 +172,6 @@ const preview = (serverToken: string, args: TemplatePreviewArguments) => { log(divider) log(`URL: ${chalk.green(url)}`) log(divider) - - open(url) }) const validateTemplateRequest = (