Compare commits

...

44 Commits

Author SHA1 Message Date
dependabot[bot] fa285fc598
Bump copy-props from 2.0.4 to 2.0.5 (#103)
Bumps [copy-props](https://github.com/gulpjs/copy-props) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/gulpjs/copy-props/releases)
- [Changelog](https://github.com/gulpjs/copy-props/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/copy-props/compare/2.0.4...2.0.5)

---
updated-dependencies:
- dependency-name: copy-props
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-23 11:36:19 +01:00
dependabot[bot] 8fb1037afa
Bump is-my-json-valid from 2.15.0 to 2.20.6 (#104)
Bumps [is-my-json-valid](https://github.com/mafintosh/is-my-json-valid) from 2.15.0 to 2.20.6.
- [Release notes](https://github.com/mafintosh/is-my-json-valid/releases)
- [Commits](https://github.com/mafintosh/is-my-json-valid/compare/v2.15.0...v2.20.6)

---
updated-dependencies:
- dependency-name: is-my-json-valid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-11 10:10:29 +01:00
dependabot[bot] 8b371bb49c
Bump trim-off-newlines from 1.0.1 to 1.0.3 (#102)
Bumps [trim-off-newlines](https://github.com/stevemao/trim-off-newlines) from 1.0.1 to 1.0.3.
- [Release notes](https://github.com/stevemao/trim-off-newlines/releases)
- [Commits](https://github.com/stevemao/trim-off-newlines/compare/v1.0.1...v1.0.3)

---
updated-dependencies:
- dependency-name: trim-off-newlines
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-11 10:10:00 +01:00
dependabot[bot] e721efe2ca
Bump node-sass from 4.14.1 to 7.0.0 (#101)
Bumps [node-sass](https://github.com/sass/node-sass) from 4.14.1 to 7.0.0.
- [Release notes](https://github.com/sass/node-sass/releases)
- [Changelog](https://github.com/sass/node-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/node-sass/compare/v4.14.1...v7.0.0)

---
updated-dependencies:
- dependency-name: node-sass
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-10 08:39:21 +01:00
Dan Mindru 96e868bdf2 Bump version 2021-09-04 09:39:10 +02:00
Anuj Punjani d7baeec5ab
Added custom inline-image plugin & modified build.js (#100)
* Modified build.js to use custom inline-image plugin
* Added inlineRemoteUrl property for inline image processing

Co-authored-by: Dan Mindru <nadurdnim@icloud.com>
2021-09-04 09:36:47 +02:00
Dan Mindru 0c1ff6d3dc
Build on PR 2021-09-01 21:18:08 +02:00
dependabot[bot] 6bc6ab5c9d
Bump postcss from 7.0.2 to 7.0.36 (#96)
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.2 to 7.0.36.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.2...7.0.36)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-16 14:39:12 +02:00
dependabot[bot] b9ef0953de
Bump normalize-url from 4.5.0 to 4.5.1 (#95)
Bumps [normalize-url](https://github.com/sindresorhus/normalize-url) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/sindresorhus/normalize-url/releases)
- [Commits](https://github.com/sindresorhus/normalize-url/commits)

---
updated-dependencies:
- dependency-name: normalize-url
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-10 12:51:54 +02:00
dependabot[bot] 5f68bf303a
Bump browserslist from 4.8.7 to 4.16.6 (#94)
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.8.7 to 4.16.6.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.8.7...4.16.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-25 16:31:55 +02:00
dependabot[bot] 5b2710d3c2
Bump lodash from 4.17.15 to 4.17.21 (#93)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 09:14:24 +02:00
dependabot[bot] deb81dd037
Bump y18n from 3.2.1 to 3.2.2 (#92)
Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-30 13:12:37 +02:00
dependabot[bot] f57fd62ce9
Bump ini from 1.3.4 to 1.3.7 (#91)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.4 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.4...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-18 15:00:51 +01:00
Ben Mares 1c27a30ab4
Fix typo which prevents clearing cached config (#90)
Closes #84
2020-10-30 08:33:35 +01:00
Dan Mindru 83c3389945 Merge branch 'master' of github.com:fadeit/responsive-html-email-signature 2020-09-21 22:38:21 +02:00
Dan Mindru aeaadaae53 Bump version 2020-09-21 22:37:49 +02:00
Dan Mindru 51857f1129
Add support for node 12 (#88)
* Add support for node 12

* Remove deps task completely
2020-09-21 22:35:10 +02:00
Dan Mindru ba355c2c78
Rework npm commands to increase windows compat (#82)
* Rework npm commands to increase windows compat

* Test up to node 12
2020-06-10 11:33:50 +02:00
dependabot[bot] 6da4298b1f
Bump acorn from 7.1.0 to 7.1.1 (#78)
Bumps [acorn](https://github.com/acornjs/acorn) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/7.1.0...7.1.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-03-16 11:31:49 +01:00
Dan Mindru 054c18687e Add TODO 2020-02-23 23:04:52 +01:00
Dan Mindru 6955a0b796 Fix checks 2020-02-23 23:02:27 +01:00
Dan Mindru a0c8b69119 Remove windiws checks, won't fix 2020-02-23 23:02:27 +01:00
Dan Mindru c0c64b3449 Remove husky 2020-02-23 23:02:27 +01:00
Dan Mindru 12ea9b461c Add open collective post install to fix failing buil 2020-02-23 23:02:27 +01:00
Dan Mindru 468418fb17 Update husky 2020-02-23 23:02:27 +01:00
Dan Mindru 3f3e92fe29 Fix tests 2020-02-23 23:02:27 +01:00
Dan Mindru 12facfd8c2 Add prettier config 2020-02-23 23:02:27 +01:00
Dan Mindru 6113d1b575 Move css link tag generation to util 2020-02-23 23:02:27 +01:00
Dan Mindru 9610d244dd Finish unused config (barely) 2020-02-23 23:02:27 +01:00
Dan Mindru f298f8bd85 Refactor for tests 2020-02-23 23:02:27 +01:00
Dan Mindru b3e0c77988 Add empty tests 2020-02-23 23:02:27 +01:00
Dan Mindru 1ad1ef9106 Update pre-push to run e2e test 2020-02-23 23:02:27 +01:00
Dan Mindru d55786f744 Check for unused initial (untested) implementation 2020-02-23 23:02:27 +01:00
Dan Mindru e599bef72e Remove unused package 2020-02-23 23:02:27 +01:00
Dan Mindru 8ba0323aaa Fix readme text 2020-02-23 23:02:27 +01:00
Dan Mindru 86944f3cee Fix tests 2020-02-23 23:02:27 +01:00
Dan Mindru e44a52c89f Re-format tests 2020-02-23 23:02:27 +01:00
Dan Mindru 8bcabaa10f Increase version (to not forget) 2020-02-23 23:02:27 +01:00
Dan Mindru d45a069c89 Sketch look for unused task 2020-02-23 23:02:27 +01:00
Dan Mindru ab54d1e3e6 Update prettier conf 2020-02-23 23:02:27 +01:00
Dan Mindru a04d8a225a Remove todos from html templates 2020-02-23 23:02:27 +01:00
Dan Mindru 7cd7e53300 Refactor build to split up reading and gettings configs into utils 2020-02-23 23:02:27 +01:00
Dan Mindru 6ab7b8d962 Format all source files 2020-02-23 23:02:27 +01:00
Dan Mindru d950d1d265 Add constants file 2020-02-23 23:02:27 +01:00
35 changed files with 19298 additions and 3997 deletions

View File

@ -4,13 +4,15 @@ module.exports = {
commonjs: true, commonjs: true,
es6: true es6: true
}, },
extends: "standard", extends: ['standard', 'prettier'],
globals: { globals: {
Atomics: "readonly", Atomics: 'readonly',
SharedArrayBuffer: "readonly" SharedArrayBuffer: 'readonly'
}, },
parserOptions: { parserOptions: {
ecmaVersion: 2018 ecmaVersion: 2018
}, },
rules: {} rules: {
semi: 0
}
}; };

View File

@ -1,6 +1,6 @@
name: Test template output name: Test template output
on: [push] on: [push, pull_request]
jobs: jobs:
test: test:
@ -9,8 +9,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node_version: [10, 11, 12, 13] node_version: [10, 11, 12, 13, 14]
# os: [ubuntu-latest, macOS-latest]
os: [ubuntu-latest, windows-latest, macOS-latest] os: [ubuntu-latest, windows-latest, macOS-latest]
steps: steps:
@ -24,7 +23,7 @@ jobs:
run: | run: |
npm -v npm -v
node -v node -v
npm install --unsafe-perm npm install
npm test npm run test
env: env:
CI: true CI: true

2
.gitignore vendored
View File

@ -3,3 +3,5 @@
/tmp/ /tmp/
.DS_Store .DS_Store
/dist /dist
IDEAS.md
npm_debug.log

2
.nvmrc
View File

@ -1 +1 @@
v10 v12

View File

@ -1,4 +1,5 @@
{ {
"semi": true, "semi": true,
"singleQuote": true "singleQuote": true,
"printWidth": 120
} }

View File

@ -1,54 +1,60 @@
# Responsive HTML email signature(s) # Responsive HTML email signature(s)
[![npm](https://img.shields.io/npm/v/responsive-html-email-signature.svg)](https://www.npmjs.com/package/responsive-html-email-signature) [![npm](https://img.shields.io/npm/v/responsive-html-email-signature.svg)](https://www.npmjs.com/package/responsive-html-email-signature)
[![license](https://img.shields.io/github/license/danmindru/responsive-html-email-signature.svg)](/LICENSE) [![license](https://img.shields.io/github/license/danmindru/responsive-html-email-signature.svg)](/LICENSE)
[![test action status](https://github.com/danmindru/responsive-html-email-signature/workflows/Test%20template%20output/badge.svg)](https://github.com/danmindru/responsive-html-email-signature/actions) [![test action status](https://github.com/danmindru/responsive-html-email-signature/workflows/Test%20template%20output/badge.svg)](https://github.com/danmindru/responsive-html-email-signature/actions)
### Let's punch email clients in the stomach 👊 ### Let's punch email clients in the stomach 👊
When you need some basic responsive email signatures that work on mobile.<br/> When you need some basic responsive email signatures that work on mobile.<br/>
...and your colleagues need them too.<br/> ...and your colleagues need them too.<br/>
...but you don't want to deal with tables and inline styles. ...but you don't want to deal with tables and inline styles.
[Read the docs in other languages](/i18n) ↗️ [Read the docs in other languages](/i18n) ↗️
## Preview ## Preview
Here are some examples: Here are some examples:
![responsive emails-01](https://cloud.githubusercontent.com/assets/1515742/10591900/13889d32-76b9-11e5-8dc0-b89d80189e93.png) ![responsive emails-01](https://cloud.githubusercontent.com/assets/1515742/10591900/13889d32-76b9-11e5-8dc0-b89d80189e93.png)
![responsive emails-02](https://cloud.githubusercontent.com/assets/1515742/10591901/139c4954-76b9-11e5-80f7-5b0ccaf5af81.png) ![responsive emails-02](https://cloud.githubusercontent.com/assets/1515742/10591901/139c4954-76b9-11e5-80f7-5b0ccaf5af81.png)
## Getting started ## Getting started
- Clone repo `git clone https://github.com/danmindru/responsive-html-email-signature.git` - Clone repo `git clone https://github.com/danmindru/responsive-html-email-signature.git`
- Run `npm install` - Run `npm install`
- Run `npm start` to generate templates from configuration. This will continue to watch your files and re-make the template until you exit. - Run `npm start` to generate templates from configuration. This will continue to watch your files and re-make the template until you exit.
### Customizing templates ### Customizing templates
- Edit files in */templates*
- Edit files in _/templates_
- Open files from `./dist` in your fav browser to check them out - Open files from `./dist` in your fav browser to check them out
> When you're done, check out [how to add them to your email client of choice](#usage-with-different-email-clients) if in doubt. > When you're done, check out [how to add them to your email client of choice](#usage-with-different-email-clients) if in doubt.
## Motivation ## Motivation
Writing HTML emails & email signatures sucks. Let's make it easier. We can't fix all email clients, but we can surely make our lives easier with some automation. <br/> Writing HTML emails & email signatures sucks. Let's make it easier. We can't fix all email clients, but we can surely make our lives easier with some automation. <br/>
## What does this pile of code do ## What does this pile of code do
- [x] generates email templates from your config - [x] generates email templates from your config
- [x] allows generating multiple templates at once (for your colleagues too!) - [x] allows generating multiple templates at once (for your colleagues too!)
- [x] transforms linked (`<link>`) CSS into inline styles - [x] transforms linked (`<link>`) CSS into inline styles
- [x] embeds local `img[src]` into the template (base64).* - [x] embeds local `img[src]` into the template (base64).\*
- [x] minifies the template - [x] minifies the template
- [x] ads some basic media queries for mail clients that support them - [x] ads some basic media queries for mail clients that support them
- [x] can build templates from multiple sources - [x] can build templates from multiple sources
- [x] watches HTML / CSS files for changes and re-builds - [x] watches HTML / CSS files for changes and re-builds
- [x] supports LESS / SASS / PostCSS - [x] supports LESS / SASS / PostCSS
- [x] autoprefixer, so you don't have to worry about your `-moz-`s or `-webkit-`s - [x] autoprefixer, so you don't have to worry about your `-moz-`s or `-webkit-`s
- [x] linting, checks for used template config parameters and more!
**Some mail clients might have [hard limits](https://support.google.com/a/answer/176652?hl=en) regarding the email size, so don't include large images if possible. If you need to, use a URL instead and host the image somewhere else.* \*_Some mail clients might have [hard limits](https://support.google.com/a/answer/176652?hl=en) regarding the email size, so don't include large images if possible. If you need to, use a URL instead and host the image somewhere else._
## Docs ## Docs
### Installing ### Installing
```bash ```bash
$ npm install $ npm install
$ npm start # By default, templates will be created in `./dist` and HTML & CSS files in './templates' will be watched for changes. $ npm start # By default, templates will be created in `./dist` and HTML & CSS files in './templates' will be watched for changes.
@ -57,9 +63,11 @@ $ npm start # By default, templates will be created in `./dist` and HTML & CSS f
> Note: works well with node v10+. Earlier and later versions might also work. > Note: works well with node v10+. Earlier and later versions might also work.
### Configuring ### Configuring
To make a basic email from existing templates, you only have to edit the `conf.json` file in each template. To make a basic email from existing templates, you only have to edit the `conf.json` file in each template.
For example, the dark template accepts the following: For example, the dark template accepts the following:
```json ```json
{ {
"id": "<will-be-used-for-filename>", "id": "<will-be-used-for-filename>",
@ -75,15 +83,15 @@ For example, the dark template accepts the following:
``` ```
### Generating multiple emails from the same config (for your colleagues too!) ### Generating multiple emails from the same config (for your colleagues too!)
To generate multiple templates, use an array instead of an object in `conf.json`, like so: To generate multiple templates, use an array instead of an object in `conf.json`, like so:
```json ```json
[ [{ ...conf1 }, { ...conf2 }]
{...conf1},
{...conf2}
]
``` ```
### Using config values in HTML ### Using config values in HTML
Config variables are made available in all HTML files. <br/> Config variables are made available in all HTML files. <br/>
Add any variable to the configuration file and use it in HTML like so: Add any variable to the configuration file and use it in HTML like so:
@ -95,23 +103,23 @@ Where the configuration contains:
```json ```json
{ {
yourCustomVariable: "Custom!" "yourCustomVariable": "Custom!"
} }
``` ```
> NB: config variables also accept HTML. That's useful for including links. > NB: config variables also accept HTML. That's useful for including links.
### Adding CSS & pre-processing ### Adding CSS & pre-processing
Any number of CSS, SASS or LESS files in a template directory & they will be automatically processed & inlined into the files outputed in `./dist`. Any number of CSS, SASS or LESS files in a template directory & they will be automatically processed & inlined into the files outputed in `./dist`.
### Multiple emails in the same template ### Multiple emails in the same template
Templates can contain multiple HTML files from which to build emails. For example, the dark template has `signature.html` and `signature-reply.html`, which is a simpler version. Templates can contain multiple HTML files from which to build emails. For example, the dark template has `signature.html` and `signature-reply.html`, which is a simpler version.
Each HTML file will be treated as an email template, except for `*.inc.html`. See below ⬇️ Each HTML file will be treated as an email template, except for `*.inc.html`. See below ⬇️
### Using partials (\*.inc.html)
### Using partials (*.inc.html)
By naming files with `*.inc.html`, they become partials. Partials will not be treated as templates (ignored), but they can be included in any HTML template using the `@include` HTML comment. By naming files with `*.inc.html`, they become partials. Partials will not be treated as templates (ignored), but they can be included in any HTML template using the `@include` HTML comment.
```html ```html
@ -122,12 +130,17 @@ By naming files with `*.inc.html`, they become partials. Partials will not be tr
Partials are useful if you have bits of HTML that repeat, like headers, footers, etc. Partials are useful if you have bits of HTML that repeat, like headers, footers, etc.
### Advanced templating
Inside HTML files, any [preprocess directive](https://github.com/jsoverson/preprocess#all-directives) is supported, such as `@if`, `@extend`, `@exec`, etc.
## Template structure (examples) ## Template structure (examples)
There are no rules regarding how to structure templates, but it's a good idea to create directories for a template group. <br/> There are no rules regarding how to structure templates, but it's a good idea to create directories for a template group. <br/>
There are 2 examples of template structures, one for the `light` email template and one for the `dark` one. There are 2 examples of template structures, one for the `light` email template and one for the `dark` one.
Here's how the dark one is structured: Here's how the dark one is structured:
```bash ```bash
./templates ./templates
├── dark ├── dark
@ -142,6 +155,7 @@ Here's how the dark one is structured:
``` ```
Here's how the light one is structured: Here's how the light one is structured:
```bash ```bash
./templates ./templates
├── light ├── light
@ -160,62 +174,70 @@ There's one convention you have to keep in mind: `all files that you wish to inc
You are of course encouraged to change the default structure for your use case. You are of course encouraged to change the default structure for your use case.
## Overview of the build process ## Overview of the build process
The diagram below shows what happens to your email templates. The diagram below shows what happens to your email templates.
Each folder in 'templates' is considered a `template group`. A template file will be generated for each of the configuration objects you add have in the template group -> `conf.js`. Each folder in 'templates' is considered a `template group`. A template file will be generated for each of the configuration objects you add have in the template group -> `conf.js`.
![Responsive HTML email template/signatures diagram](https://user-images.githubusercontent.com/1515742/45000195-35268300-afc3-11e8-82b4-7507430c48a0.png) ![Responsive HTML email template/signatures diagram](https://user-images.githubusercontent.com/1515742/45000195-35268300-afc3-11e8-82b4-7507430c48a0.png)
## CSS Support ## CSS Support
Remember, it's HTML mails, so you need to check a big-ass table to find out nothing's gonna work. Remember, it's HTML mails, so you need to check a big-ass table to find out nothing's gonna work.
See [this](https://www.campaignmonitor.com/css/) for more info. [Gulp-inline-css](https://www.npmjs.com/package/gulp-inline-css) is being used to convert whatever CSS you throw at it to inline styles, but it probably won't handle everything. See [this](https://www.campaignmonitor.com/css/) for more info. [Gulp-inline-css](https://www.npmjs.com/package/gulp-inline-css) is being used to convert whatever CSS you throw at it to inline styles, but it probably won't handle everything.
Some bonuses of using `gulp-inline-css`: many css props will be converted to attributes. For example, the 'background-color' prop will be added as 'bgcolor' attribute to table elements. Some bonuses of using `gulp-inline-css`: many css props will be converted to attributes. For example, the 'background-color' prop will be added as 'bgcolor' attribute to table elements.
For more details take a look at the [inline-css mappings](https://github.com/jonkemp/inline-css/blob/master/lib/setTableAttrs.js). For more details take a look at the [inline-css mappings](https://github.com/jonkemp/inline-css/blob/master/lib/setTableAttrs.js).
## Usage with different email clients ## Usage with different email clients
### Thunderbird ### Thunderbird
There are several Thunderbird plugins which can automatically insert signatures when composing e-mails. We recommend [SmartTemplate4](https://addons.mozilla.org/en-us/thunderbird/addon/smarttemplate4) as one of the options. It can use different templates for new e-mails, replies and forwarded e-mails. There are several Thunderbird plugins which can automatically insert signatures when composing e-mails. We recommend [SmartTemplate4](https://addons.mozilla.org/en-us/thunderbird/addon/smarttemplate4) as one of the options. It can use different templates for new e-mails, replies and forwarded e-mails.
### Gmail ### Gmail
Go to your mailbox settings & paste the generated signature. Go to your mailbox settings & paste the generated signature.
> **NB**: Gmail doesn't seem to support inlined (base64) images. You have to use absolute `http(s)//...` from them to load up.
> **NB**: Gmail doesn't seem to support inlined (base64) images. You have to use absolute `http(s)//...`.
### Office 365 / outlook.live.com ### Office 365 / outlook.live.com
It's a bit hacky to set up, but possible. See [this issue](https://github.com/danmindru/responsive-html-email-signature/issues/52). It's a bit hacky to set up, but possible. See [this issue](https://github.com/danmindru/responsive-html-email-signature/issues/52).
### Apple Mail / OS X (oh boy) ### Apple Mail / OS X (oh boy)
#### Solution 1 #### Solution 1
- Open Mail.app and go to `Mail` -> `Preferences` -> `Signatures` - Open Mail.app and go to `Mail` -> `Preferences` -> `Signatures`
- Create a new signature and write some placeholder text (doesn't matter what it is, but you have to identify it later). - Create a new signature and write some placeholder text (doesn't matter what it is, but you have to identify it later).
- Close Mail.app. - Close Mail.app.
- Open terminal, then open the signature files using TextEdit (might be different for iCloud drive check the article below). - Open terminal, then open the signature files using TextEdit (might be different for iCloud drive check the article below).
``` ```
$ open -a TextEdit ~/Library/Mobile\ Documents/com~apple~mail/Data/V3/MailData/Signatures/ubiquitous_*.mailsignature $ open -a TextEdit ~/Library/Mobile\ Documents/com~apple~mail/Data/V3/MailData/Signatures/ubiquitous_*.mailsignature
``` ```
- Keep the file with the placeholder open, close the other ones. - Keep the file with the placeholder open, close the other ones.
- Replace the `<body>...</body>` and it's contents with the template of your choice. *Don't remove the meta information at the top!* - Replace the `<body>...</body>` and it's contents with the template of your choice. _Don't remove the meta information at the top!_
- Open Mail.app and compose a new mail. Select the signature from the list to test it out. - Open Mail.app and compose a new mail. Select the signature from the list to test it out.
> **NB**: Images won't appear in the signature preview, but will work fine when you compose a message. > **NB**: Images won't appear in the signature preview, but will work fine when you compose a message.
#### Solution 2 #### Solution 2
You can also open the HTML files in `/dist` in a browser, CMD + A, CMD + C and then paste into the signature box. This won't copy the `<html>` part or the `<style>` part that includes media queries. Follow the guide if you want it.
You can also open the HTML files in `/dist` in a browser, CMD + A, CMD + C and then paste into the signature box. This won't copy the `<html>` part or the `<style>` part that includes media queries. Follow the guide if you want it.
#### Troubleshooting #### Troubleshooting
If solution #1 doesn't work, you can repeat the steps and lock the signature files before you open Mail.app again. If solution #1 doesn't work, you can repeat the steps and lock the signature files before you open Mail.app again.
Lock Files: Lock Files:
``` ```
$ chflags uchg ~/Library/Mail/V3/MailData/Signatures/*.mailsignature $ chflags uchg ~/Library/Mail/V3/MailData/Signatures/*.mailsignature
``` ```
If you want to do changes later, you have to unlock the files: If you want to do changes later, you have to unlock the files:
``` ```
$ chflags nouchg ~/Library/Mail/V3/MailData/Signatures/*.mailsignature $ chflags nouchg ~/Library/Mail/V3/MailData/Signatures/*.mailsignature
``` ```
@ -225,6 +247,7 @@ If you are using iCloud drive or having problems with it, you might also want to
### Outlook 2010 Client for Windows 7 ### Outlook 2010 Client for Windows 7
#### Solution 1 #### Solution 1
- Open Outlook 2010 and go to `File > Option > Mail > Signature` - Open Outlook 2010 and go to `File > Option > Mail > Signature`
- Create new signature (with a placeholder for your convenience) - Create new signature (with a placeholder for your convenience)
- Open signature folder using CMD - Open signature folder using CMD
@ -241,6 +264,7 @@ start Signatures
- Open Outlook again and check your signature - Open Outlook again and check your signature
#### Solution 2 #### Solution 2
Unfortnately, Outlook 2010 client dosen't support HTML file import features for your email template. But you can add your own signatures by simple Copy and paste like **Solution 2** above. Unfortnately, Outlook 2010 client dosen't support HTML file import features for your email template. But you can add your own signatures by simple Copy and paste like **Solution 2** above.
- Open built html file on `/dist` folder and Ctrl A + C - Open built html file on `/dist` folder and Ctrl A + C
@ -249,11 +273,12 @@ Unfortnately, Outlook 2010 client dosen't support HTML file import features for
> **NB**: base 64 will not be shown on Outlook 2010 client. So, I recommend to use external url if you want to use images. > **NB**: base 64 will not be shown on Outlook 2010 client. So, I recommend to use external url if you want to use images.
## Other commands ## Other commands
### `npm run test` ### `npm run test`
Runs tests once. Runs tests once.
### `npm run once` ### `npm run once`
Creates templates and exits; does not watch files. Creates templates and exits; does not watch files.

6
constants.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
SOURCE_DIR: 'templates',
DIST_DIR: 'dist',
WORKING_DIR: 'tmp',
CONFIGURATION_FILE: 'conf.json'
}

View File

@ -1,13 +1,13 @@
'use strict'; const gulp = require('gulp');
const plumber = require('gulp-plumber');
const gulp = require('gulp'), const { SOURCE_DIR, DIST_DIR, WORKING_DIR, CONFIGURATION_FILE } = require('./constants');
fs = require('fs'),
plumber = require('gulp-plumber');
const options = { const options = {
source: 'templates', sourceDir: SOURCE_DIR,
dist: 'dist', distDir: DIST_DIR,
workingDir: 'tmp', workingDir: WORKING_DIR,
configurationFile: CONFIGURATION_FILE,
src: function plumbedSrc() { src: function plumbedSrc() {
return gulp.src.apply(gulp, arguments).pipe(plumber()); return gulp.src.apply(gulp, arguments).pipe(plumber());
} }
@ -16,35 +16,35 @@ const options = {
/** /**
* Load tasks from the '/tasks' directory. * Load tasks from the '/tasks' directory.
*/ */
const build = require('./tasks/build')(options); require('./tasks/build')(options);
const checkDeps = require('./tasks/check-deps')(options); require('./tasks/dupe')(options);
const dupe = require('./tasks/dupe')(options); require('./tasks/less')(options);
const less = require('./tasks/less')(options); require('./tasks/lint')(options);
const lint = require('./tasks/lint')(options); require('./tasks/postcss')(options);
const postcss = require('./tasks/postcss')(options); require('./tasks/sass')(options);
const sass = require('./tasks/sass')(options); require('./tasks/check-for-unused').checkForUnusedTask(options);
/* Runs the entire pipeline once. */ /* Runs the entire pipeline once. */
gulp.task('run-pipeline', gulp.series('dupe', 'less', 'sass', 'postcss', 'lint', 'build')); gulp.task(
'run-pipeline',
gulp.series('dupe', 'less', 'sass', 'postcss', 'lint', 'build', gulp.parallel('check-for-unused'))
);
/* By default templates will be built into '/dist'. */ /* By default templates will be built into '/dist'. */
gulp.task( gulp.task(
'default', 'default',
gulp.series( gulp.series('run-pipeline', () => {
'run-pipeline',
() => {
/* gulp will watch for changes in '/templates'. */ /* gulp will watch for changes in '/templates'. */
gulp.watch( gulp.watch(
[ [
options.source + '/**/*.html', options.sourceDir + '/**/*.html',
options.source + '/**/*.css', options.sourceDir + '/**/*.css',
options.source + '/**/*.scss', options.sourceDir + '/**/*.scss',
options.source + '/**/*.less', options.sourceDir + '/**/*.less',
options.source + '/**/conf.json' options.sourceDir + '/**/conf.json'
], ],
{ delay: 500 }, { delay: 500 },
gulp.series('run-pipeline') gulp.series('run-pipeline')
) );
} })
)
); );

21992
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "responsive-html-email-signature", "name": "responsive-html-email-signature",
"version": "5.1.1", "version": "6.1.0",
"description": "Responsive template for emails & email signatures.", "description": "Responsive template for emails & email signatures.",
"main": "index.js", "main": "index.js",
"repository": { "repository": {
@ -23,24 +23,27 @@
}, },
"homepage": "https://github.com/danmindru/responsive-html-email-signature#readme", "homepage": "https://github.com/danmindru/responsive-html-email-signature#readme",
"scripts": { "scripts": {
"start": "node ./node_modules/.bin/gulp", "start": "./node_modules/.bin/gulp",
"once": "node ./node_modules/.bin/gulp run-pipeline", "once": "./node_modules/.bin/gulp run-pipeline",
"deploy": "npm run test && cp -r dist demo && git push origin `git subtree split --prefix demo develop`:gh-pages --force", "deploy": "npm run test && cp -r dist demo && git push origin `git subtree split --prefix demo develop`:gh-pages --force",
"test": "npm run once && node ./node_modules/.bin/ava", "test": "npm run once && npm run _test",
"test:watch": "npm run once && node ./node_modules/.bin/ava --watch", "test:watch": "npm run once && npm run _test:watch",
"format": "node ./node_modules/.bin/prettier {tasks,tests}/**/*.js gulpfile.js .eslintrc.js --write", "format": "./node_modules/.bin/prettier {tasks,tests}/**/*.js gulpfile.js .eslintrc.js --write",
"lint": "node ./node_modules/.bin/eslint ./**/*.js gulpfile.js" "lint": "./node_modules/.bin/eslint ./**/*.js gulpfile.js",
"_test": "./node_modules/.bin/ava",
"_test:watch": "./node_modules/.bin/ava --watch"
}, },
"dependencies": { "dependencies": {
"autoprefixer": "^9.7.4", "autoprefixer": "^9.6.1",
"chalk": "^2.4.2",
"cheerio": "^0.22.0",
"del": "^5.1.0", "del": "^5.1.0",
"graceful-fs": "^4.2.3",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-autoprefixer": "^7.0.1", "gulp-autoprefixer": "^7.0.1",
"gulp-david": "^1.0.1", "gulp-david": "^1.0.1",
"gulp-inline-css": "^3.5.0", "gulp-inline-css": "^3.5.0",
"gulp-inline-images-no-http": "^1.3.0", "gulp-inline-images-no-http": "^1.3.3",
"gulp-jsonlint": "^1.3.1", "gulp-jsonlint": "^1.3.2",
"gulp-less": "^4.0.1", "gulp-less": "^4.0.1",
"gulp-minify-html": "~1.0.5", "gulp-minify-html": "~1.0.5",
"gulp-minify-inline": "^1.1.0", "gulp-minify-inline": "^1.1.0",
@ -48,33 +51,44 @@
"gulp-postcss": "^8.0.0", "gulp-postcss": "^8.0.0",
"gulp-preprocess": "^3.0.3", "gulp-preprocess": "^3.0.3",
"gulp-rename": "^2.0.0", "gulp-rename": "^2.0.0",
"gulp-sass": "^4.0.2", "gulp-sass": "^4.1.0",
"klaw": "^3.0.0", "klaw": "^3.0.0",
"node-sass": "^4.13.1" "node-sass": "^7.0.0",
}, "plugin-error": "^1.0.1",
"resolutions": { "through2": "^2.0.5"
"graceful-fs": "^4.1.15"
}, },
"devDependencies": { "devDependencies": {
"ava": "^3.4.0", "ava": "^2.4.0",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-config-standard": "^14.1.0", "eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.20.1", "eslint-config-standard": "^14.1.1",
"eslint-plugin-node": "^11.0.0", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"gulp-cli": "^2.2.0", "graceful-fs": "^4.2.4",
"husky": "^3.0.5", "gulp-cli": "^2.3.0",
"opencollective-postinstall": "^2.0.3",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"pretty-quick": "^2.0.1" "pretty-quick": "^2.0.1"
}, },
"resolutions": {
"graceful-fs": "^4.2.4",
"vinyl-fs": "^3.0.3"
},
"browserslist": [ "browserslist": [
"last 5 versions" "last 5 versions"
], ],
"husky": { "husky": {
"hooks": { "hooks": {
"pre-push": "npm test", "pre-push": "npm run test",
"pre-commit": "npm run lint && pretty-quick --staged" "pre-commit": "npm run lint && node ./node_modules/.bin/pretty-quick --staged --pattern ./**/*.js"
} }
},
"ava": {
"helpers": [
"**/util.js"
]
} }
} }

View File

@ -1,23 +1,16 @@
'use strict'; const gulp = require('gulp');
const inlineCss = require('gulp-inline-css');
const gulp = require('gulp'), const minifyHTML = require('gulp-minify-html');
inlineCss = require('gulp-inline-css'), const minifyInline = require('gulp-minify-inline');
minifyHTML = require('gulp-minify-html'), const preprocess = require('gulp-preprocess');
minifyInline = require('gulp-minify-inline'), const rename = require('gulp-rename');
preprocess = require('gulp-preprocess'), const del = require('del');
rename = require('gulp-rename'), const { inlineImg } = require('./check-for-image-url');
klaw = require('klaw'), const { getConfigsForDir, getFilePathsForDir, getCssLinkTagsFromFilelist } = require('./util/util');
fs = require('fs'),
del = require('del'),
jsonlint = require('jsonlint'),
inlineimg = require('gulp-inline-images-no-http'),
path = require('path');
function buildTask(options) { function buildTask(options) {
// Requires: 'dupe', 'less', 'sass', 'postcss', 'lint'. // Requires: 'dupe', 'less', 'sass', 'postcss', 'lint'.
gulp.task( gulp.task('build', function build(done) {
'build',
function build(done) {
/** /**
* Makes templates for a given directory & its configurations. * Makes templates for a given directory & its configurations.
* *
@ -26,45 +19,22 @@ function buildTask(options) {
* @param {Array} confItems A list of configurations objects (usually persons) to make templates from. * @param {Array} confItems A list of configurations objects (usually persons) to make templates from.
*/ */
function makeTemplates(dir, confItems) { function makeTemplates(dir, confItems) {
confItems.forEach(function handleConf(conf) { return confItems.map(async conf => {
var cwd = options.workingDir + '/' + dir; const cwd = `${options.workingDir}/${dir}`;
var stylesheets = [];
/** /**
* Find stylesheets relative to the CWD & generate <link> tags. * Find stylesheets relative to the CWD & generate <link> tags.
* This way we can automagically inject them into <head>. * This way we can automagically inject them into <head>.
*/ */
klaw(cwd) const files = await getFilePathsForDir(cwd);
.on('readable', function walkTemplateDir() { const context = Object.assign(conf, {
var stylesheet; stylesheets: getCssLinkTagsFromFilelist(files)
});
while ((stylesheet = this.read())) { return options
var relativePath = __dirname.substring(0, __dirname.lastIndexOf('/')) + '/tmp/' + dir;
stylesheets.push(stylesheet.path.replace(relativePath, ''));
}
})
.on('end', function finishedTemplateDirWalk() {
const context = Object.assign(
conf,
{
stylesheets: stylesheets
.filter(function filterFiles (file) {
/* Read only CSS files. */
return file.match(/.*\.css/) ? true : false;
})
.reduce(function (prev, current, index, acc) {
var cssPath = path.win32.basename(current);
return (prev += '<link rel="stylesheet" href="' + cssPath + '">');
}, '')
}
);
options
.src([cwd + '/**/*.html', '!' + cwd + '/**/*.inc.html']) .src([cwd + '/**/*.html', '!' + cwd + '/**/*.inc.html'])
.pipe( .pipe(preprocess({ context }))
preprocess({ context }) .pipe(inlineImg({ getHTTP: confItems[0]['inlineRemoteUrl'] }))
)
.pipe(inlineimg())
.pipe( .pipe(
inlineCss({ inlineCss({
applyTableAttributes: true, applyTableAttributes: true,
@ -82,44 +52,26 @@ function buildTask(options) {
return path; return path;
}) })
) )
.pipe(gulp.dest(options.dist)); .pipe(gulp.dest(options.distDir));
});
}); });
} }
/* Clean up & then read 'src' to generate templates (build entry point). */ /*
return del(options.dist) * Clean up & then read from workingDir to generate templates.
.then(function buildStart() { * For each found config, a template group will be generated through `makeTemplates`.
*/
return del(options.distDir)
.then(() => {
/** /**
* Loop through dirs and load their conf files. * Loop through dirs and load their conf files.
* Promisify all 'makeTemplate' calls and when resolved, let gulp know we're done. * Promisify all 'makeTemplate' calls and when resolved, let gulp know we're done.
*/ */
var files = []; const configs = getConfigsForDir(options.workingDir, options.configurationFile);
var promises = []; return Promise.all(configs.map(({ dir, confItems }) => makeTemplates(dir, confItems)));
fs.readdirSync('./' + options.workingDir).forEach(function readConfigurations(dir) {
/** NB: For 'watch' to properly work, the cache needs to be deleted before each require. */
var confPath = '../tmp/' + dir + '/conf.json';
var current = null;
var confItems;
delete require.cache[require.resolve(confPath)];
current = require(confPath);
if (current && current.length) {
confItems = [...current];
} else {
confItems = [current];
}
promises.push(makeTemplates(dir, confItems));
});
Promise.all(promises);
}) })
.then(() => done()) .then(() => done())
.catch((err) => console.log(err)); .catch(err => console.log(err));
} });
);
} }
module.exports = buildTask; module.exports = buildTask;

View File

@ -1,14 +0,0 @@
'use strict';
const gulp = require('gulp'),
david = require('gulp-david');
function checkDepsTask(){
gulp.task('check-deps', function checkDeps(){
gulp
.src('package.json')
.pipe(david());
});
}
module.exports = checkDepsTask;

View File

@ -0,0 +1,155 @@
'use strict';
const https = require('https');
const http = require('http');
const path = require('path');
const url = require('url');
const fs = require('fs');
const PluginError = require('plugin-error');
const through = require('through2');
const cheerio = require('cheerio');
const { log } = require('./util/util');
const PLUGIN_NAME = 'gulp-inline-images';
const MIME_TYPE_REGEX = /.+\/([^\s]*)/;
const INLINE_ATTR = 'inline';
const NOT_INLINE_ATTR = `!${INLINE_ATTR}`;
function inlineImg(options = {}) {
const selector = options.selector || 'img[src]';
const attribute = options.attribute || 'src';
const getHTTP = options.getHTTP || false;
return through.obj(function(file, encoding, callback) {
if (file.isStream()) {
this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
return callback();
}
if (file.isBuffer()) {
const contents = file.contents.toString(encoding);
// Load it into cheerio's virtual DOM for easy manipulation
const $ = cheerio.load(contents);
const inlineFlag = $(`img[${INLINE_ATTR}]`);
// If images with an inline attr are found that is the selection we want
const imgTags = inlineFlag.length ? inlineFlag : $(selector);
let count = 0;
imgTags.each(function() {
const $img = $(this);
const src = $img.attr(attribute);
// Save the file format from the extension
const extFormat = path.extname(src).substr(1);
// If inlineFlag tags were found we want to remove the inline tag
if (inlineFlag.length) {
$img.removeAttr(INLINE_ATTR);
}
// Find !inline attribute
const notInlineFlag = $img.attr(NOT_INLINE_ATTR);
if (typeof notInlineFlag !== typeof undefined && notInlineFlag !== false) {
// Remove the tag and don't process this file
return $img.removeAttr(NOT_INLINE_ATTR);
}
// Count async ops
count++;
getSrcBase64(options.basedir || file.base, getHTTP, src, (err, result, resFormat, skipFormatting) => {
if (err) {
log.warn(`Failed to load http image. Check the format of ${src}.`);
log.error(err);
} else {
// Need a format in and a result for this to work
if (!skipFormatting) {
if (result && (extFormat || resFormat)) {
$img.attr('src', `data:image/${extFormat};base64,${result}`);
} else {
$img.attr('src', ``);
$img.attr('alt', `Image not found, Please check Url`);
log.warn(`Failed to read image. Check the format of ${src}.`);
}
}
if (!--count) {
file.contents = Buffer.from($.html());
callback(null, file);
}
}
});
});
// If no files are processing we don't need to wait as none were ever started
if (!imgTags.length) {
file.contents = Buffer.from($.html());
callback(null, file);
}
}
});
}
function getHTTPBase64(url, callback) {
// Get applicable library
const lib = url.startsWith('https') ? https : http;
// Initiate a git request to our URL
const req = lib.get(url, res => {
// Check for redirect
if (res.statusCode >= 301 && res.statusCode < 400 && res.headers.location) {
// Redirect
return getHTTPBase64(res.headers.location, callback);
}
// Check for HTTP errors
if (res.statusCode < 200 || res.statusCode >= 400) {
return callback(new Error('Failed to load page, status code: ' + res.statusCode));
}
// Get file format
let format;
if (res.headers['content-type']) {
const matches = res.headers['content-type'].match(MIME_TYPE_REGEX);
if (matches) {
format = matches[1];
}
}
// Create an empty buffer to store the body in
let body = Buffer.from([]);
// Append each chunk to the body
res.on('data', chunk => (body = Buffer.concat([body, chunk])));
// Done callback
res.on('end', () => callback(null, body.toString('base64'), format));
});
// Listen for network errors
req.on('error', err => callback(err));
}
function getSrcBase64(base, getHTTP, src, callback) {
// TODO: @deprecated — since v11.0.0 url.parse should be replaced with url.URL() ctor
if (!url.parse(src).hostname) {
// Get local file
const filePath = path.join(base, src);
if (fs.existsSync(filePath)) {
fs.readFile(filePath, 'base64', callback);
} else {
callback(null);
}
} else {
// Get remote file
if (getHTTP) {
return getHTTPBase64(src, callback);
} else {
callback(null, src, null, true);
}
}
}
module.exports = {
inlineImg,
getHTTPBase64,
getSrcBase64
};

View File

@ -0,0 +1,10 @@
const gulp = require('gulp');
function checkForUnusedTask(options) {
gulp.task('check-for-missing', async done => {
// TODO
done();
});
}
module.exports = checkForUnusedTask;

75
tasks/check-for-unused.js Normal file
View File

@ -0,0 +1,75 @@
const gulp = require('gulp');
const chalk = require('chalk');
const { getConfigsForDir, getFilePathsForDir, getHtmlTemplatesFromFilelist, log } = require('./util/util');
const OUTPUT_KEYWORD = '@echo';
function checkForUnusedTask(options) {
gulp.task('check-for-unused', async done => {
const configs = getConfigsForDir(options.workingDir, options.configurationFile);
const unusedItems = await checkForUnusedItemsInConfigs(options.workingDir, configs);
outputWarningsForUnusedItems(unusedItems, configs);
done();
});
}
/**
* Outputs warnings for unused items.
*
* @param { Array<Array<string>> } unusedItems
* @param { Array<object> } configs
*/
const outputWarningsForUnusedItems = (unusedItems, configs) => {
const find = OUTPUT_KEYWORD;
const regex = new RegExp(find, 'g');
unusedItems.forEach((unusedInConfigs, index) => {
const { dir } = configs[index];
unusedInConfigs.forEach(unusedInConfItems => {
const unusedItemsToLog = unusedInConfItems
.filter(item => item !== `${OUTPUT_KEYWORD} id`)
.filter(item => item !== '@echo inlineRemoteUrl');
if (unusedItemsToLog.length) {
log.warn(
`${unusedItemsToLog.length} unused properties in ${dir}: ${unusedItemsToLog
.reduce((acc, cur) => (acc ? `${acc}, ${chalk.white(cur)}` : chalk.white(cur)), '')
.replace(regex, '')}`
);
}
});
});
};
/**
* In a directory, checks for unused configs.
*
* @param { string } rootDir
* @param { Array } configs Array of configs.
*/
const checkForUnusedItemsInConfigs = (rootDir, configs) =>
Promise.all(
configs.map(async ({ dir, confItems }) => {
return Promise.all(
confItems.map(async confItem => {
const definedStrings = Object.keys(confItem).map(key => `${OUTPUT_KEYWORD} ${key}`);
const cwd = `${rootDir}/${dir}`;
const files = await getFilePathsForDir(cwd);
const htmlTemplates = await getHtmlTemplatesFromFilelist(files);
const concatenatedTemplates = htmlTemplates.join('');
return definedStrings.filter(str => !concatenatedTemplates.includes(str));
})
);
})
);
const self = {
checkForUnusedTask,
outputWarningsForUnusedItems,
checkForUnusedItemsInConfigs
};
module.exports = self;

View File

@ -1,15 +1,11 @@
'use strict'; const gulp = require('gulp');
const del = require('del');
const gulp = require('gulp'), function dupeTask(options) {
del = require('del'); gulp.task('dupe', function() {
function dupeTask(options){
gulp.task('dupe', function(){
del.sync([options.workingDir]); del.sync([options.workingDir]);
return options return options.src([options.sourceDir + '/**/*']).pipe(gulp.dest('./' + options.workingDir));
.src([options.source + '/**/*'])
.pipe(gulp.dest('./' + options.workingDir));
}); });
} }

View File

@ -1,23 +1,18 @@
'use strict'; const gulp = require('gulp');
const less = require('gulp-less');
const autoprefixer = require('gulp-autoprefixer');
const rename = require('gulp-rename');
const gulp = require('gulp'), function lessTask(options) {
less = require('gulp-less'),
autoprefixer = require('gulp-autoprefixer'),
rename = require('gulp-rename');
function lessTask(options){
// Requires: dupe. // Requires: dupe.
gulp.task( gulp.task('less', function() {
'less',
function() {
return options return options
.src(options.workingDir + '/**/*.less') .src(options.workingDir + '/**/*.less')
.pipe(less()) .pipe(less())
.pipe(autoprefixer()) .pipe(autoprefixer())
.pipe(rename({ extname: '.css' })) .pipe(rename({ extname: '.css' }))
.pipe(gulp.dest(options.workingDir)); .pipe(gulp.dest(options.workingDir));
} });
);
} }
module.exports = lessTask; module.exports = lessTask;

View File

@ -1,19 +1,14 @@
'use strict'; const gulp = require('gulp');
const jsonlint = require('gulp-jsonlint');
const gulp = require('gulp'), function lintTask(options) {
jsonlint = require("gulp-jsonlint");
function lintTask(options){
// Requiers: dupe. // Requiers: dupe.
gulp.task( gulp.task('lint', function() {
'lint',
function() {
return options return options
.src(options.workingDir + '/**/conf.json') .src(options.workingDir + '/**/conf.json')
.pipe(jsonlint()) .pipe(jsonlint())
.pipe(jsonlint.reporter()); .pipe(jsonlint.reporter());
} });
);
} }
module.exports = lintTask; module.exports = lintTask;

View File

@ -1,24 +1,17 @@
'use strict'; const gulp = require('gulp');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const gulp = require('gulp'), function postcssTask(options) {
postcss = require('gulp-postcss'),
autoprefixer = require('autoprefixer');
function postcssTask(options){
// Requires: dupe. // Requires: dupe.
gulp.task( gulp.task('postcss', function() {
'postcss', var processors = [autoprefixer()];
function() {
var processors = [
autoprefixer()
];
return options return options
.src(options.workingDir + '/**/*.css') .src(options.workingDir + '/**/*.css')
.pipe(postcss(processors)) .pipe(postcss(processors))
.pipe(gulp.dest(options.workingDir)); .pipe(gulp.dest(options.workingDir));
} });
);
} }
module.exports = postcssTask; module.exports = postcssTask;

View File

@ -1,9 +1,7 @@
'use strict'; const gulp = require('gulp');
const autoprefixer = require('gulp-autoprefixer');
const gulp = require('gulp'), const sass = require('gulp-sass');
autoprefixer = require('gulp-autoprefixer'), const rename = require('gulp-rename');
sass = require('gulp-sass'),
rename = require('gulp-rename');
function sassTask(options) { function sassTask(options) {
// Requires: dupe. // Requires: dupe.
@ -13,9 +11,7 @@ function sassTask(options) {
return options return options
.src(options.workingDir + '/**/*.scss') .src(options.workingDir + '/**/*.scss')
.pipe(sass()) .pipe(sass())
.pipe( .pipe(autoprefixer())
autoprefixer()
)
.pipe(rename({ extname: '.css' })) .pipe(rename({ extname: '.css' }))
.pipe(gulp.dest(options.workingDir)); .pipe(gulp.dest(options.workingDir));
}) })

131
tasks/util/util.js Normal file
View File

@ -0,0 +1,131 @@
const fs = require('fs');
const path = require('path');
const klaw = require('klaw');
const chalk = require('chalk');
// todo test
/**
* Given a directory, scans all directories in it (not deep) and returns found config items.
*
* @param { string } rootDir Dir to look into.
* @param { string } configFileName Files to look for in each dir, i.e. conf.json
*/
const getConfigsForDir = (rootDir, configFileName) => {
return fs
.readdirSync(rootDir)
.map(dir => {
const confPath = `${dir}/${configFileName}`;
/** Exit with warn if no configuration file found. */
if (!fs.existsSync(path.resolve(rootDir, confPath))) {
self.log.warn(`Missing configuration in "${dir}". Did you remember to create "${dir}/${configFileName}"?`);
return false;
}
let current = null;
let confItems;
const resolvedPath = path.resolve(rootDir, confPath);
delete require.cache[resolvedPath]; // NB: For 'watch' to properly work, the cache needs to be deleted before each require.
current = require(resolvedPath);
// Handle single objects or arrays of configs.
if (current && current.length) {
confItems = [...current];
} else {
confItems = [current];
}
return {
dir,
confItems
};
})
.filter(config => config);
};
/**
* Given a directory, gets all file paths in it.
*
* @param { string } dir Dir to get files paths for.
*/
const getFilePathsForDir = dir => {
const files = [];
return new Promise(resolve => {
klaw(dir)
.on('readable', function walkTemplateDir() {
let file;
while ((file = this.read())) {
const relativePath = `${__dirname.substring(0, __dirname.lastIndexOf('/'))}/${dir}`;
files.push(file.path.replace(relativePath, ''));
}
})
.on('end', function finishedTemplateDirWalk() {
resolve(files);
});
});
};
/**
* Gets an array of html files in a filelist.
*
* @param { Array } filelist
*/
const getHtmlTemplatesFromFilelist = filelist => {
return Promise.all(
filelist
.filter(file => file.match(/.*\.html/) || file.match(/.*\.inc*\.html/))
.map(
htmlTemplate =>
new Promise((resolve, reject) => {
fs.readFile(htmlTemplate, 'utf8', (error, data) => {
if (error) {
reject(error);
}
resolve(data);
});
})
)
);
};
/**
* Gets an array of css link tags from a filelist (if css files are in the filelist).
*
* @param { Array } filelist
*/
const getCssLinkTagsFromFilelist = filelist => {
return filelist
.filter(file => !!file.match(/.*\.css/)) // Read only CSS files.
.reduce((acc, cur) => {
const cssPath = path.win32.basename(cur);
return (acc += '<link rel="stylesheet" href="' + cssPath + '">');
}, '');
};
const log = {
warn: (...messages) => {
console.warn('🔵 ', chalk.yellow(messages));
},
log: (...messages) => {
console.log('🔘 ', chalk.gray(messages));
},
error: (...messages) => {
console.error('🔴 ', chalk.red(messages));
}
};
const self = {
log,
getConfigsForDir,
getFilePathsForDir,
getHtmlTemplatesFromFilelist,
getCssLinkTagsFromFilelist
};
module.exports = self;

View File

@ -12,42 +12,46 @@ body {
-webkit-text-size-adjust: none; -webkit-text-size-adjust: none;
} }
tr{ tr {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
box-sizing: border-box; box-sizing: border-box;
font-size: 14px; font-size: 14px;
line-height: 22px; line-height: 22px;
} }
.main{ .main {
width:100%; width: 100%;
background-color: #ffffff; background-color: #ffffff;
} }
.rbcc{ .rbcc {
/* /*
* rbcc -> reset - border - cellspacing - cellpading * rbcc -> reset - border - cellspacing - cellpading
* *
* Resets table attributes. * Resets table attributes.
*/ */
border:0; border: 0;
cellpadding:0; cellpadding: 0;
cellspacing:0; cellspacing: 0;
} }
.sp{ /* Separator tr; props are actually contained by it's inner element atm. */ } .sp {
/* Separator tr; props are actually contained by it's inner element atm. */
}
.sp__inner{ .sp__inner {
padding: 15px 0; padding: 15px 0;
} }
.spd{ /* Separator tr (double); props are actually contained by it's inner element atm. */ } .spd {
/* Separator tr (double); props are actually contained by it's inner element atm. */
}
.spd__inner{ .spd__inner {
height: 60px; height: 60px;
} }
a{ a {
text-decoration: none; text-decoration: none;
color: #0fade1; color: #0fade1;
} }
@ -57,7 +61,7 @@ a{
* 2. Content styles. * 2. Content styles.
* ================== * ==================
*/ */
.main__welcome{ .main__welcome {
color: #000; color: #000;
padding: 10px 30px 0 30px; padding: 10px 30px 0 30px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
@ -65,93 +69,92 @@ a{
line-height: 22px; line-height: 22px;
} }
.main__content{ .main__content {
color: #000; color: #000;
padding: 10px 30px 0 30px; padding: 10px 30px 0 30px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px; font-size: 14px;
} }
/* /*
* ================ * ================
* 3. Footer styles. * 3. Footer styles.
* ================ * ================
*/ */
.footer{ .footer {
background-color: #303030; background-color: #303030;
padding: 20px 30px 0px 30px; padding: 20px 30px 0px 30px;
color: #f5f5f5; color: #f5f5f5;
border-top: 8px solid #585858; border-top: 8px solid #585858;
} }
.footer a{ .footer a {
color: #f5f5f5; color: #f5f5f5;
} }
.footer--simple{ .footer--simple {
padding-bottom: 20px; padding-bottom: 20px;
background-color: #FFFFFF; background-color: #ffffff;
} }
.footer--simple tr td{ .footer--simple tr td {
color: #888; color: #888;
} }
.footer__main{ .footer__main {
/* This style property fucks up the width on OS X, needs to be *JUST* attribute */ /* This style property fucks up the width on OS X, needs to be *JUST* attribute */
width:100%; width: 100%;
} }
.footer__main__signature{ .footer__main__signature {
font-size: 14px; font-size: 14px;
color: #f5f5f5; color: #f5f5f5;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:left; align: left;
} }
.footer__main__col1{ .footer__main__col1 {
width:70%; width: 70%;
margin-bottom:40px; margin-bottom: 40px;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:left; align: left;
} }
.footer__main__col1__td{ .footer__main__col1__td {
color: #9E9E9E; color: #9e9e9e;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:left; align: left;
padding-top: 15px; padding-top: 15px;
} }
.footer__main__col1__td > span{ .footer__main__col1__td > span {
font-size:18px; font-size: 18px;
margin-bottom:5px; margin-bottom: 5px;
} }
.footer__main a > span{ .footer__main a > span {
/* Revert apple blue-link style. */ /* Revert apple blue-link style. */
color: #f5f5f5!important; color: #f5f5f5 !important;
text-decoration:none!important; text-decoration: none !important;
} }
.footer__main__col2{ .footer__main__col2 {
width:30%; width: 30%;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:right; align: right;
} }
.footer__main__col2__td{ .footer__main__col2__td {
font-size: 14px; font-size: 14px;
color: #f5f5f5; color: #f5f5f5;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:right; align: right;
} }
.footer__main__col2__td__img{ .footer__main__col2__td__img {
border: 0; border: 0;
padding-left:20px; padding-left: 20px;
max-width: 100%; max-width: 100%;
max-height:65px; max-height: 65px;
height: auto; height: auto;
} }

View File

@ -1,31 +1,28 @@
<td class="footer"> <td class="footer">
<table class="rbcc footer__main"> <table class="rbcc footer__main">
<tr> <tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__signature" align="left"> <td class="footer__main__signature" align="left">
<!-- @echo signature --><br /> <!-- @echo signature --><br />
<strong><!-- @echo name --></strong><br /> <strong><!-- @echo name --></strong><br />
</td> </td>
</tr> </tr>
<tr class="spd"><td class="spd__inner"></td></tr> <tr class="spd">
<td class="spd__inner"></td>
</tr>
<tr> <tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<table class="rbcc footer__main__col2" align="right"> <table class="rbcc footer__main__col2" align="right">
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__col2__td" align="right"> <td class="footer__main__col2__td" align="right">
<a href="<!-- @echo website -->"> <a href="<!-- @echo website -->">
<img src="<!-- @echo logoUrl -->" alt="<!-- @echo logoAlt -->" class="footer__main__col2__td__img"/> <img src="<!-- @echo logoUrl -->" alt="<!-- @echo logoAlt -->" class="footer__main__col2__td__img" />
</a> </a>
</td> </td>
</table> </table>
<!-- @todo gulp-inline-css doesn't parse align -->
<table class="rbcc footer__main__col1" align="left"> <table class="rbcc footer__main__col1" align="left">
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__col1__td" align="left"> <td class="footer__main__col1__td" align="left">
<span><!-- @echo slogan --></span><br/> <span><!-- @echo slogan --></span><br />
<!-- @echo contactMain --> <!-- @echo contactMain -->
<a href="mailto:<!-- @echo contactMail -->" target="_blank"><!-- @echo contactMail --></a> <a href="mailto:<!-- @echo contactMail -->" target="_blank"><!-- @echo contactMail --></a>
</td> </td>

View File

@ -1,16 +1,18 @@
<!-- @include head.inc.html --> <!-- @include head.inc.html -->
<body> <body>
<br/> <!-- <br/> Makes it easier to add text when composing --> <br />
<!-- <br/> Makes it easier to add text when composing -->
<table class="main rbcc"> <table class="main rbcc">
<tr class="sp"><td class="sp__inner"></td></tr> <tr class="sp">
<td class="sp__inner"></td>
</tr>
<tr class="rbcc"> <tr class="rbcc">
<td class="footer footer--simple"> <td class="footer footer--simple">
<table class="rbcc footer__main"> <table class="rbcc footer__main">
<tr> <tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__signature" align="left"> <td class="footer__main__signature" align="left">
<!-- @echo signature --><br /> <!-- @echo signature --><br />
<!-- @echo name --><br /> <!-- @echo name --><br />

View File

@ -1,33 +1,29 @@
<td class="footer"> <td class="footer">
<table class="rbcc footer__main"> <table class="rbcc footer__main">
<tr> <tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__signature" align="left"> <td class="footer__main__signature" align="left">
<!-- @echo signature --><br /> <!-- @echo signature --><br />
<!-- @echo name --><br /> <!-- @echo name --><br />
</td> </td>
</tr> </tr>
<tr class="gray-hr"> <tr class="gray-hr">
<td><hr class="gray-hr"/></td> <td><hr class="gray-hr" /></td>
</tr> </tr>
<tr> <tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<table class="rbcc footer__main__col1" align="left"> <table class="rbcc footer__main__col1" align="left">
<td class="footer__main__col1__td" align="left"> <td class="footer__main__col1__td" align="left">
<strong> <strong>
<!-- @echo contactMain --> <!-- @echo contactMain -->
<a href="mailto:<!-- @echo contactMail -->" target="_blank"><!-- @echo contactMail --></a> <a href="mailto:<!-- @echo contactMail -->" target="_blank"><!-- @echo contactMail --></a> </strong
</strong><br /> ><br />
<!-- @echo contactSecondary --> <!-- @echo contactSecondary -->
</td> </td>
</table> </table>
<!-- @todo gulp-inline-css doesn't parse align -->
<table class="rbcc footer__main__col2" align="right"> <table class="rbcc footer__main__col2" align="right">
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__col2__td" align="right"> <td class="footer__main__col2__td" align="right">
<a href="<!-- @echo website -->"> <a href="<!-- @echo website -->">
<img src="<!-- @echo logoUrl -->" alt="<!-- @echo logoAlt -->" class="footer__main__col2__td__img"/> <img src="<!-- @echo logoUrl -->" alt="<!-- @echo logoAlt -->" class="footer__main__col2__td__img" />
</a> </a>
</td> </td>
</table> </table>

View File

@ -12,56 +12,60 @@ body {
-webkit-text-size-adjust: none; -webkit-text-size-adjust: none;
} }
tr{ tr {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
box-sizing: border-box; box-sizing: border-box;
font-size: 14px; font-size: 14px;
line-height: 20px; line-height: 20px;
} }
.rbcc{ .rbcc {
/* /*
* rbcc -> reset - border - cellspacing - cellpading * rbcc -> reset - border - cellspacing - cellpading
* *
* Resets table attributes. * Resets table attributes.
*/ */
border:0; border: 0;
cellpadding:0; cellpadding: 0;
cellspacing:0; cellspacing: 0;
} }
.background{ .background {
width:100%;
}
.main{
width:100%;
background-color: #ffffff;
padding-top:15px;
}
.sp{ /* Separator tr; props are actually contained by it's inner element atm. */ }
.sp__inner{
padding: 15px 0;
}
.gray-hr{ /* 100% width light grey line; props are actually contained by inner elements atm. */ }
.gray-hr td{
width: 100%; width: 100%;
} }
.gray-hr hr{ .main {
border-bottom:1px solid #E4E4E4; width: 100%;
border-top:none; background-color: #ffffff;
margin-bottom:20px; padding-top: 15px;
margin-top:20px;
color: transparent;
background:transparent;
} }
a{ .sp {
/* Separator tr; props are actually contained by it's inner element atm. */
}
.sp__inner {
padding: 15px 0;
}
.gray-hr {
/* 100% width light grey line; props are actually contained by inner elements atm. */
}
.gray-hr td {
width: 100%;
}
.gray-hr hr {
border-bottom: 1px solid #e4e4e4;
border-top: none;
margin-bottom: 20px;
margin-top: 20px;
color: transparent;
background: transparent;
}
a {
text-decoration: none; text-decoration: none;
color: #0fade1; color: #0fade1;
} }
@ -71,7 +75,7 @@ a{
* 2. Content styles. * 2. Content styles.
* ================== * ==================
*/ */
.main__welcome{ .main__welcome {
color: #000; color: #000;
padding: 10px 30px 0 30px; padding: 10px 30px 0 30px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
@ -79,85 +83,84 @@ a{
line-height: 22px; line-height: 22px;
} }
.main__content{ .main__content {
color: #000; color: #000;
padding: 10px 30px 0 30px; padding: 10px 30px 0 30px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px; font-size: 14px;
} }
/* /*
* ================ * ================
* 3. Footer styles. * 3. Footer styles.
* ================ * ================
*/ */
.footer{ .footer {
background-color: #f5f5f5; background-color: #f5f5f5;
padding: 20px 30px 0px 30px; padding: 20px 30px 0px 30px;
color: #888; color: #888;
border-top: 8px solid #EAEAEA; border-top: 8px solid #eaeaea;
} }
.footer a{ .footer a {
color: #888; color: #888;
} }
.footer--simple{ .footer--simple {
padding-bottom: 20px; padding-bottom: 20px;
background-color: #FFFFFF; background-color: #ffffff;
} }
.footer__main{ .footer__main {
/* NB: This prop fucks up the width on OS X, needs to be *JUST* attribute. */ /* NB: This prop fucks up the width on OS X, needs to be *JUST* attribute. */
width:100%; width: 100%;
} }
.footer__main__signature{ .footer__main__signature {
font-size: 14px; font-size: 14px;
color: #888; color: #888;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:left; align: left;
} }
.footer__main__col1{ .footer__main__col1 {
width:70%; width: 70%;
margin-bottom:30px; margin-bottom: 30px;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:left; align: left;
} }
.footer__main__col1__td{ .footer__main__col1__td {
font-size: 14px; font-size: 14px;
color: #888; color: #888;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:left; align: left;
} }
.footer__main a > span{ .footer__main a > span {
/* Revert apple blue-link style. */ /* Revert apple blue-link style. */
color: #888!important; color: #888 !important;
text-decoration:none!important; text-decoration: none !important;
} }
.footer__main__col2{ .footer__main__col2 {
width:30%; width: 30%;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:right; align: right;
} }
.footer__main__col2__td{ .footer__main__col2__td {
font-size: 14px; font-size: 14px;
color: #888; color: #888;
/* @todo gulp-inline-css doesn't parse align */ /* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align:right; align: right;
} }
.footer__main__col2__td__img{ .footer__main__col2__td__img {
border: 0; border: 0;
padding-top: 6px; padding-top: 6px;
padding-left:10px; padding-left: 10px;
max-width: 100%; max-width: 100%;
max-height:38px; max-height: 38px;
height: auto; height: auto;
} }

View File

@ -1,15 +1,16 @@
<!-- @include head.inc.html --> <!-- @include head.inc.html -->
<body> <body>
<br/> <br />
<table class="main rbcc"> <table class="main rbcc">
<tr class="sp"><td class="sp__inner"></td></tr> <tr class="sp">
<td class="sp__inner"></td>
</tr>
<tr class="rbcc"> <tr class="rbcc">
<td class="footer footer--simple"> <td class="footer footer--simple">
<table class="rbcc footer__main"> <table class="rbcc footer__main">
<tr> <tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__signature" align="left"> <td class="footer__main__signature" align="left">
<!-- @echo signature --><br /> <!-- @echo signature --><br />
<!-- @echo name --><br /> <!-- @echo name --><br />

View File

@ -0,0 +1,37 @@
const test = require('ava');
const { readFileSync } = require('../util');
test('dark signature output', async t => {
const expected = readFileSync('tests/sample/dark/signature-dark.html');
const built = readFileSync('dist/dark/signature-dark.html');
t.deepEqual(expected, built);
});
test('dark signature reply output', async t => {
const expected = readFileSync('tests/sample/dark/signature-reply-dark.html');
const built = readFileSync('dist/dark/signature-reply-dark.html');
t.deepEqual(expected, built);
});
test('light signature output', async t => {
const expected = readFileSync('tests/sample/light/signature-light.html');
const built = readFileSync('dist/light/signature-light.html');
t.deepEqual(expected, built);
});
test('light signature reply output', async t => {
const expected = readFileSync('tests/sample/light/signature-reply-light.html');
const built = readFileSync('dist/light/signature-reply-light.html');
t.deepEqual(expected, built);
});
test('light full mail output', async t => {
const expected = readFileSync('tests/sample/light/full-mail-light.html');
const built = readFileSync('dist/light/full-mail-light.html');
t.deepEqual(expected, built);
});

View File

@ -1,38 +0,0 @@
const test = require('ava');
const fs = require('fs');
const path = require('path');
test('dark signature output', async (t) => {
const expected = fs.readFileSync(path.resolve('tests/sample/dark/signature-dark.html'));
const built = fs.readFileSync(path.resolve('dist/dark/signature-dark.html'));
t.deepEqual(expected, built);
});
test('dark signature reply output', async (t) => {
const expected = fs.readFileSync(path.resolve('tests/sample/dark/signature-reply-dark.html'));
const built = fs.readFileSync(path.resolve('dist/dark/signature-reply-dark.html'));
t.deepEqual(expected, built);
});
test('light signature output', async (t) => {
const expected = fs.readFileSync(path.resolve('tests/sample/light/signature-light.html'));
const built = fs.readFileSync(path.resolve('dist/light/signature-light.html'));
t.deepEqual(expected, built);
});
test('light signature reply output', async (t) => {
const expected = fs.readFileSync(path.resolve('tests/sample/light/signature-reply-light.html'));
const built = fs.readFileSync(path.resolve('dist/light/signature-reply-light.html'));
t.deepEqual(expected, built);
});
test('light full mail output', async (t) => {
const expected = fs.readFileSync(path.resolve('tests/sample/light/full-mail-light.html'));
const built = fs.readFileSync(path.resolve('dist/light/full-mail-light.html'));
t.deepEqual(expected, built);
});

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta name="viewport" content="width=device-width"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><style type="text/css">@media only screen and (max-width:560px){.footer td{font-size:12px!important}.footer__main__col1{width:100%!important}.footer__main__col1__td{text-align:left}.footer__main__col1__td>span{margin-bottom:10px}.footer__main__col2{width:100%!important}.footer__main__col2__td{text-align:left;padding-bottom:20px}.footer__main__col2__td__img{padding-left:0!important}}</style></head><body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"><br><table class="main rbcc" style="border: 0; cellpadding: 0; cellspacing: 0;" border="0" cellpadding="0" cellspacing="0" bgcolor="#ffffff" width="100%"><tr class="sp" style="-webkit-box-sizing: border-box; box-sizing: border-box; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 22px;"><td class="sp__inner" style="padding: 15px 0;"></td></tr><tr class="rbcc" style="-webkit-box-sizing: border-box; border: 0; box-sizing: border-box; cellpadding: 0; cellspacing: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 22px;"><td class="footer footer--simple" style="border-top: 8px solid #585858; color: #f5f5f5; padding: 20px 30px 0px 30px; padding-bottom: 20px;" bgcolor="#FFFFFF"><table class="rbcc footer__main" style="border: 0; cellpadding: 0; cellspacing: 0;" border="0" cellpadding="0" cellspacing="0" width="100%"><tr style="-webkit-box-sizing: border-box; box-sizing: border-box; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 22px;"><td class="footer__main__signature" align="left" style="align: left; color: #888; font-size: 14px;">Best regards,<br>The dark mail team<br></td></tr></table></td></tr></table></body></html> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta name="viewport" content="width=device-width"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><style type="text/css">@media only screen and (max-width:560px){.footer td{font-size:12px!important}.footer__main__col1{width:100%!important}.footer__main__col1__td{text-align:left}.footer__main__col1__td>span{margin-bottom:10px}.footer__main__col2{width:100%!important}.footer__main__col2__td{text-align:left;padding-bottom:20px}.footer__main__col2__td__img{padding-left:0!important}}</style></head><body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"><br><table class="main rbcc" style="border: 0; cellpadding: 0; cellspacing: 0;" border="0" cellpadding="0" cellspacing="0" bgcolor="#ffffff" width="100%"><tr class="sp" style="-webkit-box-sizing: border-box; box-sizing: border-box; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 22px;"><td class="sp__inner" style="padding: 15px 0;"></td></tr><tr class="rbcc" style="-webkit-box-sizing: border-box; border: 0; box-sizing: border-box; cellpadding: 0; cellspacing: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 22px;"><td class="footer footer--simple" style="border-top: 8px solid #585858; color: #f5f5f5; padding: 20px 30px 0px 30px; padding-bottom: 20px;" bgcolor="#ffffff"><table class="rbcc footer__main" style="border: 0; cellpadding: 0; cellspacing: 0;" border="0" cellpadding="0" cellspacing="0" width="100%"><tr style="-webkit-box-sizing: border-box; box-sizing: border-box; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 22px;"><td class="footer__main__signature" align="left" style="align: left; color: #888; font-size: 14px;">Best regards,<br>The dark mail team<br></td></tr></table></td></tr></table></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta name="viewport" content="width=device-width"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><style type="text/css">@media only screen and (max-width:480px){.footer td{font-size:12px!important}.footer__main__col1{width:100%!important}.footer__main__col2{width:100%!important}.footer__main__col2__td{text-align:left;padding-bottom:20px}.footer__main__col2__td__img{padding-left:0!important}.gray-hr hr{margin-bottom:10px!important;margin-top:10px!important}}@media only screen and (min-width:1025px){.body-with-bg{background-color:#f1f1f1}.body-with-bg .main{border:1px solid #e9e9e9!important;max-width:960px;margin:0 auto}.background{padding:30px;background-color:#f1f1f1}}</style></head><body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"><br><table class="main rbcc" style="border: 0; cellpadding: 0; cellspacing: 0; padding-top: 15px;" border="0" cellpadding="0" cellspacing="0" bgcolor="#ffffff" width="100%"><tr class="sp" style="-webkit-box-sizing: border-box; box-sizing: border-box; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px;"><td class="sp__inner" style="padding: 15px 0;"></td></tr><tr class="rbcc" style="-webkit-box-sizing: border-box; border: 0; box-sizing: border-box; cellpadding: 0; cellspacing: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px;"><td class="footer footer--simple" style="border-top: 8px solid #EAEAEA; color: #888; padding: 20px 30px 0px 30px; padding-bottom: 20px;" bgcolor="#FFFFFF"><table class="rbcc footer__main" style="border: 0; cellpadding: 0; cellspacing: 0;" border="0" cellpadding="0" cellspacing="0" width="100%"><tr style="-webkit-box-sizing: border-box; box-sizing: border-box; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px;"><td class="footer__main__signature" align="left" style="align: left; color: #888; font-size: 14px;">Yours truly,<br>The light mail team<br></td></tr></table></td></tr></table></body></html> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta name="viewport" content="width=device-width"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><style type="text/css">@media only screen and (max-width:480px){.footer td{font-size:12px!important}.footer__main__col1{width:100%!important}.footer__main__col2{width:100%!important}.footer__main__col2__td{text-align:left;padding-bottom:20px}.footer__main__col2__td__img{padding-left:0!important}.gray-hr hr{margin-bottom:10px!important;margin-top:10px!important}}@media only screen and (min-width:1025px){.body-with-bg{background-color:#f1f1f1}.body-with-bg .main{border:1px solid #e9e9e9!important;max-width:960px;margin:0 auto}.background{padding:30px;background-color:#f1f1f1}}</style></head><body style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;"><br><table class="main rbcc" style="border: 0; cellpadding: 0; cellspacing: 0; padding-top: 15px;" border="0" cellpadding="0" cellspacing="0" bgcolor="#ffffff" width="100%"><tr class="sp" style="-webkit-box-sizing: border-box; box-sizing: border-box; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px;"><td class="sp__inner" style="padding: 15px 0;"></td></tr><tr class="rbcc" style="-webkit-box-sizing: border-box; border: 0; box-sizing: border-box; cellpadding: 0; cellspacing: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px;"><td class="footer footer--simple" style="border-top: 8px solid #eaeaea; color: #888; padding: 20px 30px 0px 30px; padding-bottom: 20px;" bgcolor="#ffffff"><table class="rbcc footer__main" style="border: 0; cellpadding: 0; cellspacing: 0;" border="0" cellpadding="0" cellspacing="0" width="100%"><tr style="-webkit-box-sizing: border-box; box-sizing: border-box; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px;"><td class="footer__main__signature" align="left" style="align: left; color: #888; font-size: 14px;">Yours truly,<br>The light mail team<br></td></tr></table></td></tr></table></body></html>

5
tests/util.js Normal file
View File

@ -0,0 +1,5 @@
const fs = require('fs');
module.exports = {
readFileSync: path => fs.readFileSync(('./', path), 'utf8')
};