Compare commits

...

91 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
Dan Mindru 4adabe4ecd Rollback husky 2020-02-23 22:28:18 +01:00
Dan Mindru 77953b721a Nuke package lock 2020-02-23 22:22:51 +01:00
Dan Mindru f68b5104ce Drop v9 2020-02-23 22:17:52 +01:00
Dan Mindru 3c22a3921c Last chance for windows 2020-02-23 22:13:08 +01:00
Dan Mindru fedff3d67c Include node v13 2020-02-23 22:11:09 +01:00
Dan Mindru abecdb6371 Remove windows from tests 2020-02-23 22:09:06 +01:00
Dan Mindru 31f824b664 Tweaks scripts 2020-02-23 22:08:24 +01:00
Dan Mindru 6f05af60ad Update node-sass 2020-02-23 21:59:11 +01:00
Dan Mindru 0150856d77 Try to install on win using unsafe perm 2020-02-23 21:55:15 +01:00
Dan Mindru 1c6ff143b1 Test on node 9-12 2020-02-23 21:50:02 +01:00
Dan Mindru 5fcbf1ca19 Downgrade node-sass 2020-02-23 21:47:10 +01:00
Dan Mindru 6ecb318ca6 Fix action node version 2020-02-23 21:41:03 +01:00
Dan Mindru 455999b910 Test on LTS node only 2020-02-23 21:37:33 +01:00
Dan Mindru 4618b491d7 Rollback gulp-rename 2020-02-23 21:32:50 +01:00
Dan Mindru d1d656d375 Roll back ava to prevent broken tests on windows 2020-02-23 21:25:37 +01:00
Dan Mindru be239820d1 Include cli as dep 2020-02-23 21:19:03 +01:00
Dan Mindru 46011d12ea Update dark test fixutre 2020-02-23 21:18:44 +01:00
Dan Mindru 0edb498614 Include semis 2020-02-23 21:18:34 +01:00
Dan Mindru 51702a2c0f Include node 13 in test matrix 2020-02-23 21:11:02 +01:00
Dan Mindru 2a43f4309d Remove newline eof 2020-02-23 21:09:56 +01:00
Dan Mindru 9e1780eb08 Update dependencies 2020-02-23 21:03:37 +01:00
Dan Mindru 8ecbee58c0
Add cname (#75) 2019-09-08 21:50:04 +02:00
Dan Mindru 54ba657926
Setup eslint, prettier & husky (#72) 2019-09-03 21:20:18 +02:00
Dan Mindru 9c2e12104c
Set linguist-vendored to true 2019-09-03 13:33:23 +02:00
Dan Mindru 0b869c8359 Merge branch 'master' of github.com:danmindru/responsive-html-email-signature 2019-09-03 12:48:25 +02:00
Dan Mindru 3f6dc14c78 Fix typo 2019-09-03 12:48:11 +02:00
Dan Mindru c177e1f24f
Fix other node-version to node_version typo 2019-09-03 11:47:54 +02:00
Dan Mindru 265f955af5 Merge branch 'master' of github.com:danmindru/responsive-html-email-signature 2019-09-03 11:37:05 +02:00
Dan Mindru 5bac55caac Don't use spread to increase backwards compat 2019-09-03 11:36:49 +02:00
Dan Mindru 1dece49925
Fix typo: node-version to node_version 2019-09-03 11:23:41 +02:00
Dan Mindru 1d00da17ff
Add action badge 2019-09-03 11:21:23 +02:00
Dan Mindru 356ddd41cb Merge branch 'master' of github.com:danmindru/responsive-html-email-signature 2019-09-03 11:16:06 +02:00
Dan Mindru f8687ca6d9 Update documentation 2019-09-03 11:13:33 +02:00
Dan Mindru 80160e8135
Use node number syntax as opposed to x.x 2019-09-03 11:00:55 +02:00
Dan Mindru b316aa6caa
Run tests on windows, mac & ubuntu 2019-09-03 10:49:22 +02:00
Dan Mindru 558ccc2a9e
Rename test action 2019-09-03 10:46:53 +02:00
Dan Mindru 2970388579 Bump version 2019-09-03 10:44:06 +02:00
Dan Mindru 53226eec93 Update readme 2019-09-03 10:43:42 +02:00
Dan Mindru 06f7ac73fe Resolve paths in test 2019-09-03 10:43:33 +02:00
Dan Mindru ead960958b Create a gulp task to run pipeline once 2019-09-03 10:43:11 +02:00
Dan Mindru 41f6dced8f Refactor build to use native promises 2019-09-03 10:42:46 +02:00
Dan Mindru d8baffdca4
Set linguist-vendored to false 2019-09-02 17:05:31 +02:00
Dan Mindru d9d966c1f7
Add wildcard to vendored paths 2019-09-02 17:02:22 +02:00
Dan Mindru dd9f8ef381
Add sample data as vendor code 2019-09-02 17:01:36 +02:00
Dan Mindru ad525373b7
Create test workflow for github actions 2019-08-31 17:42:55 +02:00
Dan Mindru 284d97efa5 Bump version 2019-08-31 17:30:04 +02:00
Dan Mindru bbd274c7f7 Update packages 2019-08-31 17:29:39 +02:00
37 changed files with 19896 additions and 3555 deletions

18
.eslintrc.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es6: true
},
extends: ['standard', 'prettier'],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parserOptions: {
ecmaVersion: 2018
},
rules: {
semi: 0
}
};

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
demo/* linguist-vendored
tests/sample/* linguist-vendored

29
.github/workflows/nodejs.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Test template output
on: [push, pull_request]
jobs:
test:
name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
node_version: [10, 11, 12, 13, 14]
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node_version }}
- name: npm install & test
run: |
npm -v
node -v
npm install
npm run test
env:
CI: true

4
.gitignore vendored
View File

@ -2,4 +2,6 @@
/node_modules/
/tmp/
.DS_Store
/dist
/dist
IDEAS.md
npm_debug.log

2
.nvmrc
View File

@ -1 +1 @@
v10
v12

5
.prettierrc Normal file
View File

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

1
CNAME Normal file
View File

@ -0,0 +1 @@
responsive-html-email-signature-generator.com

111
README.md
View File

@ -1,66 +1,73 @@
# 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)
[![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)
### Let's punch email clients in the stomach 👊
When you need some basic responsive email signatures that work on mobile.<br/>
...and your colleagues need them too.<br/>
...but you don't want to deal with tables and inline styles.
[Read the docs in other languages](/i18n) ↗️
## Preview
Here are some examples:
![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)
## Getting started
- Clone repo `git clone https://github.com/danmindru/responsive-html-email-signature.git`
- Run `npm install`
- Run `gulp` to generate templates from configuration (one time)
- 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
- Edit files in */templates*
- Run `gulp watch` to watch templates and re-generate when changed
- Edit files in _/templates_
- 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.
## 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/>
## What does this pile of code do
- [x] generates email templates from your config
- [x] allows generating multiple templates at once (for your colleagues too!)
- [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] ads some basic media queries for mail clients that support them
- [x] can build templates from multiple sources
- [x] watches HTML / CSS files for changes and re-builds
- [x] supports LESS / SASS / PostCSS
- [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 don't support them, so an external URL might be a good idea. Also, some clients might complain about the size, so keep an eye out.*
\*_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
### Installing
```bash
$ npm install
$ gulp # or npm run create
# By default, 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.
```
> Note: works well with node v10+. Earlier versions might also work.
> Note: works well with node v10+. Earlier and later versions might also work.
### Configuring
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:
```json
{
"id": "<will-be-used-for-filename>",
@ -76,15 +83,15 @@ For example, the dark template accepts the following:
```
### Generating multiple emails from the same config (for your colleagues too!)
Use an array instead of object in `conf.json`, having multiple configs like the one above:
To generate multiple templates, use an array instead of an object in `conf.json`, like so:
```json
[
{...conf1},
{...conf2}
]
[{ ...conf1 }, { ...conf2 }]
```
### Using config values in HTML
Config variables are made available in all HTML files. <br/>
Add any variable to the configuration file and use it in HTML like so:
@ -92,34 +99,48 @@ Add any variable to the configuration file and use it in HTML like so:
<p><!-- @echo yourCustomVariable --></p>
```
> NB: config variables accept HTML.
Where the configuration contains:
```json
{
"yourCustomVariable": "Custom!"
}
```
> NB: config variables also accept HTML. That's useful for including links.
### 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`.
### 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 ⬇️
### Using partials (\*.inc.html)
### Partials (*.inc.html)
If you indeed have multiple emails in the same templates, you can be sure some of the HTML repeats.
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.
Luckily, partials can be used for common parts (i.e. headers, footers).
Partials *will not* be treated as an email template, but ignored when built. They can however be included in other HTML files, like so:
```html
<section>
<!-- @include footer.inc.html -->
</section
```
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)
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.
Here's how the dark one looks:
Here's how the dark one is structured:
```bash
./templates
├── dark
@ -133,7 +154,8 @@ Here's how the dark one looks:
├── signature-reply.html # Simplified signature (loads head)
```
Here's how the light one looks:
Here's how the light one is structured:
```bash
./templates
├── light
@ -148,68 +170,74 @@ Here's how the light one looks:
├── signature-reply.html # Simplified signature (loads head)
```
Files are included via [gulp-preprocess](https://www.npmjs.com/package/gulp-preprocess).
There's one convention you have to keep in mind: `all files that you wish to include should follow the *.inc.html format`. The gulp task ignores `*.inc.html` files, but will try to process & create email templates from all `.html` files.
You are of course encouraged to change the default structure for your use case.
## Overview of the build process
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`.
![Responsive HTML email template/signatures diagram](https://user-images.githubusercontent.com/1515742/45000195-35268300-afc3-11e8-82b4-7507430c48a0.png)
## CSS Support
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.
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).
## Usage with different email clients
### 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.
### Gmail
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
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)
#### Solution 1
- 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).
- Close Mail.app.
- 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
```
- 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.
> **NB**: Images won't appear in the signature preview, but will work fine when you compose a message.
#### 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
If solution #1 doesn't work, you can repeat the steps and lock the signature files before you open Mail.app again.
Lock Files:
```
$ chflags uchg ~/Library/Mail/V3/MailData/Signatures/*.mailsignature
```
If you want to do changes later, you have to unlock the files:
```
$ chflags nouchg ~/Library/Mail/V3/MailData/Signatures/*.mailsignature
```
@ -219,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
#### Solution 1
- Open Outlook 2010 and go to `File > Option > Mail > Signature`
- Create new signature (with a placeholder for your convenience)
- Open signature folder using CMD
@ -235,6 +264,7 @@ start Signatures
- Open Outlook again and check your signature
#### 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.
- Open built html file on `/dist` folder and Ctrl A + C
@ -243,3 +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.
## Other commands
### `npm run test`
Runs tests once.
### `npm run once`
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'),
fs = require('fs'),
plumber = require('gulp-plumber');
const { SOURCE_DIR, DIST_DIR, WORKING_DIR, CONFIGURATION_FILE } = require('./constants');
const options = {
source: 'templates',
dist: 'dist',
workingDir: 'tmp',
sourceDir: SOURCE_DIR,
distDir: DIST_DIR,
workingDir: WORKING_DIR,
configurationFile: CONFIGURATION_FILE,
src: function plumbedSrc() {
return gulp.src.apply(gulp, arguments).pipe(plumber());
}
@ -16,32 +16,35 @@ const options = {
/**
* Load tasks from the '/tasks' directory.
*/
const build = require('./tasks/build')(options);
const checkDeps = require('./tasks/check-deps')(options);
const dupe = require('./tasks/dupe')(options);
const less = require('./tasks/less')(options);
const lint = require('./tasks/lint')(options);
const postcss = require('./tasks/postcss')(options);
const sass = require('./tasks/sass')(options);
require('./tasks/build')(options);
require('./tasks/dupe')(options);
require('./tasks/less')(options);
require('./tasks/lint')(options);
require('./tasks/postcss')(options);
require('./tasks/sass')(options);
require('./tasks/check-for-unused').checkForUnusedTask(options);
/* By default templates will be built into '/dist' */
/* Runs the entire pipeline once. */
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'. */
gulp.task(
'default',
gulp.series(
('dupe', 'less', 'sass', 'postcss', 'lint', 'build'),
() => {
/* gulp will watch for changes in '/templates'. */
gulp.watch(
[
options.source + '/**/*.html',
options.source + '/**/*.css',
options.source + '/**/*.scss',
options.source + '/**/*.less',
options.source + '/**/conf.json'
],
{ delay: 500 },
gulp.series('dupe', 'less', 'sass', 'postcss', 'lint', 'build')
)
}
)
gulp.series('run-pipeline', () => {
/* gulp will watch for changes in '/templates'. */
gulp.watch(
[
options.sourceDir + '/**/*.html',
options.sourceDir + '/**/*.css',
options.sourceDir + '/**/*.scss',
options.sourceDir + '/**/*.less',
options.sourceDir + '/**/conf.json'
],
{ delay: 500 },
gulp.series('run-pipeline')
);
})
);

22067
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",
"version": "5.0.0",
"version": "6.1.0",
"description": "Responsive template for emails & email signatures.",
"main": "index.js",
"repository": {
@ -22,38 +22,73 @@
"url": "https://github.com/danmindru/responsive-html-email-signature/issues"
},
"homepage": "https://github.com/danmindru/responsive-html-email-signature#readme",
"scripts": {
"start": "./node_modules/.bin/gulp",
"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",
"test": "npm run once && npm run _test",
"test:watch": "npm run once && npm run _test:watch",
"format": "./node_modules/.bin/prettier {tasks,tests}/**/*.js gulpfile.js .eslintrc.js --write",
"lint": "./node_modules/.bin/eslint ./**/*.js gulpfile.js",
"_test": "./node_modules/.bin/ava",
"_test:watch": "./node_modules/.bin/ava --watch"
},
"dependencies": {
"autoprefixer": "^9.6.1",
"chalk": "^2.4.2",
"cheerio": "^0.22.0",
"del": "^5.1.0",
"graceful-fs": "^4.2.2",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^7.0.0",
"gulp-autoprefixer": "^7.0.1",
"gulp-david": "^1.0.1",
"gulp-inline-css": "^3.3.2",
"gulp-inline-images-no-http": "^1.3.0",
"gulp-jsonlint": "^1.3.1",
"gulp-inline-css": "^3.5.0",
"gulp-inline-images-no-http": "^1.3.3",
"gulp-jsonlint": "^1.3.2",
"gulp-less": "^4.0.1",
"gulp-minify-html": "~1.0.5",
"gulp-minify-inline": "^1.1.0",
"gulp-plumber": "^1.2.1",
"gulp-postcss": "^8.0.0",
"gulp-preprocess": "^3.0.2",
"gulp-rename": "^1.4.0",
"gulp-sass": "^4.0.2",
"gulp-preprocess": "^3.0.3",
"gulp-rename": "^2.0.0",
"gulp-sass": "^4.1.0",
"klaw": "^3.0.0",
"node-sass": "^4.12.0",
"q": "^1.5.1"
},
"resolutions": {
"graceful-fs": "^4.1.15"
},
"scripts": {
"create": "gulp",
"deploy": "cp -r dist demo && git push origin `git subtree split --prefix demo develop`:gh-pages --force",
"test": "node ./node_modules/.bin/ava"
"node-sass": "^7.0.0",
"plugin-error": "^1.0.1",
"through2": "^2.0.5"
},
"devDependencies": {
"ava": "^2.3.0"
"ava": "^2.4.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.11.0",
"eslint-config-standard": "^14.1.1",
"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-standard": "^4.0.1",
"graceful-fs": "^4.2.4",
"gulp-cli": "^2.3.0",
"opencollective-postinstall": "^2.0.3",
"prettier": "^1.19.1",
"pretty-quick": "^2.0.1"
},
"browserslist": ["last 5 versions"]
"resolutions": {
"graceful-fs": "^4.2.4",
"vinyl-fs": "^3.0.3"
},
"browserslist": [
"last 5 versions"
],
"husky": {
"hooks": {
"pre-push": "npm run test",
"pre-commit": "npm run lint && node ./node_modules/.bin/pretty-quick --staged --pattern ./**/*.js"
}
},
"ava": {
"helpers": [
"**/util.js"
]
}
}

View File

@ -1,121 +1,77 @@
'use strict';
const gulp = require('gulp'),
inlineCss = require('gulp-inline-css'),
minifyHTML = require('gulp-minify-html'),
minifyInline = require('gulp-minify-inline'),
preprocess = require('gulp-preprocess'),
rename = require('gulp-rename'),
klaw = require('klaw'),
fs = require('fs'),
Q = require('q'),
del = require('del'),
jsonlint = require('jsonlint'),
inlineimg = require('gulp-inline-images-no-http'),
path = require('path');
const gulp = require('gulp');
const inlineCss = require('gulp-inline-css');
const minifyHTML = require('gulp-minify-html');
const minifyInline = require('gulp-minify-inline');
const preprocess = require('gulp-preprocess');
const rename = require('gulp-rename');
const del = require('del');
const { inlineImg } = require('./check-for-image-url');
const { getConfigsForDir, getFilePathsForDir, getCssLinkTagsFromFilelist } = require('./util/util');
function buildTask(options) {
// Requires: 'dupe', 'less', 'sass', 'postcss', 'lint'.
gulp.task(
'build',
function build(done) {
/** Makes templates for a given directory & its configurations.
* @function makeTemplates
* @param {String} dir Directory to make templates from.
* @param {Array} confItems A list of configurations objects (usually persons) to make templates from.
*/
function makeTemplates(dir, confItems) {
confItems.forEach(function handleConf(conf) {
var cwd = options.workingDir + '/' + dir;
var stylesheets = [];
gulp.task('build', function build(done) {
/**
* Makes templates for a given directory & its configurations.
*
* @function makeTemplates
* @param {String} dir Directory to make templates from.
* @param {Array} confItems A list of configurations objects (usually persons) to make templates from.
*/
function makeTemplates(dir, confItems) {
return confItems.map(async conf => {
const cwd = `${options.workingDir}/${dir}`;
/**
* Find stylesheets relative to the CWD & generate <link> tags.
* This way we can automagically inject them into <head>.
*/
klaw(cwd)
.on('readable', function walkTemplateDir() {
var stylesheet;
while ((stylesheet = this.read())) {
var relativePath = __dirname.substring(0, __dirname.lastIndexOf('/')) + '/tmp/' + dir;
stylesheets.push(stylesheet.path.replace(relativePath, ''));
}
})
.on('end', function finishedTemplateDirWalk() {
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'])
.pipe(
preprocess({
context: conf
})
)
.pipe(inlineimg())
.pipe(
inlineCss({
applyTableAttributes: true,
applyWidthAttributes: true,
preserveMediaQueries: true,
removeStyleTags: false
})
)
.pipe(minifyHTML({ quotes: true }))
.pipe(minifyInline())
.pipe(
rename(function rename(path) {
path.dirname = dir;
path.basename += '-' + conf.id;
return path;
})
)
.pipe(gulp.dest(options.dist));
});
/**
* Find stylesheets relative to the CWD & generate <link> tags.
* This way we can automagically inject them into <head>.
*/
const files = await getFilePathsForDir(cwd);
const context = Object.assign(conf, {
stylesheets: getCssLinkTagsFromFilelist(files)
});
}
/** Clean up & then read 'src' to generate templates (build entry point). */
del(options.dist)
.then(function buildStart() {
/**
* Loop through dirs and load their conf files.
* Promisify all 'makeTemplate' calls and when resolved, make a call to the task (`cb`) to let gulp know we're done.
*/
var files = [];
var promises = [];
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));
});
Q.all(promises);
})
.then(() => done())
.catch((err) => console.log(err));
return options
.src([cwd + '/**/*.html', '!' + cwd + '/**/*.inc.html'])
.pipe(preprocess({ context }))
.pipe(inlineImg({ getHTTP: confItems[0]['inlineRemoteUrl'] }))
.pipe(
inlineCss({
applyTableAttributes: true,
applyWidthAttributes: true,
preserveMediaQueries: true,
removeStyleTags: false
})
)
.pipe(minifyHTML({ quotes: true }))
.pipe(minifyInline())
.pipe(
rename(function rename(path) {
path.dirname = dir;
path.basename += '-' + conf.id;
return path;
})
)
.pipe(gulp.dest(options.distDir));
});
}
);
/*
* Clean up & then read from workingDir to generate templates.
* 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.
* Promisify all 'makeTemplate' calls and when resolved, let gulp know we're done.
*/
const configs = getConfigsForDir(options.workingDir, options.configurationFile);
return Promise.all(configs.map(({ dir, confItems }) => makeTemplates(dir, confItems)));
})
.then(() => done())
.catch(err => console.log(err));
});
}
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'),
del = require('del');
function dupeTask(options){
gulp.task('dupe', function(){
function dupeTask(options) {
gulp.task('dupe', function() {
del.sync([options.workingDir]);
return options
.src([options.source + '/**/*'])
.pipe(gulp.dest('./' + options.workingDir));
return options.src([options.sourceDir + '/**/*']).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'),
less = require('gulp-less'),
autoprefixer = require('gulp-autoprefixer'),
rename = require('gulp-rename');
function lessTask(options){
function lessTask(options) {
// Requires: dupe.
gulp.task(
'less',
function() {
return options
.src(options.workingDir + '/**/*.less')
.pipe(less())
.pipe(autoprefixer())
.pipe(rename({ extname: '.css' }))
.pipe(gulp.dest(options.workingDir));
}
);
gulp.task('less', function() {
return options
.src(options.workingDir + '/**/*.less')
.pipe(less())
.pipe(autoprefixer())
.pipe(rename({ extname: '.css' }))
.pipe(gulp.dest(options.workingDir));
});
}
module.exports = lessTask;

View File

@ -1,19 +1,14 @@
'use strict';
const gulp = require('gulp');
const jsonlint = require('gulp-jsonlint');
const gulp = require('gulp'),
jsonlint = require("gulp-jsonlint");
function lintTask(options){
function lintTask(options) {
// Requiers: dupe.
gulp.task(
'lint',
function() {
return options
.src(options.workingDir + '/**/conf.json')
.pipe(jsonlint())
.pipe(jsonlint.reporter());
}
);
gulp.task('lint', function() {
return options
.src(options.workingDir + '/**/conf.json')
.pipe(jsonlint())
.pipe(jsonlint.reporter());
});
}
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'),
postcss = require('gulp-postcss'),
autoprefixer = require('autoprefixer');
function postcssTask(options){
function postcssTask(options) {
// Requires: dupe.
gulp.task(
'postcss',
function() {
var processors = [
autoprefixer()
];
gulp.task('postcss', function() {
var processors = [autoprefixer()];
return options
.src(options.workingDir + '/**/*.css')
.pipe(postcss(processors))
.pipe(gulp.dest(options.workingDir));
}
);
return options
.src(options.workingDir + '/**/*.css')
.pipe(postcss(processors))
.pipe(gulp.dest(options.workingDir));
});
}
module.exports = postcssTask;

View File

@ -1,9 +1,7 @@
'use strict';
const gulp = require('gulp'),
autoprefixer = require('gulp-autoprefixer'),
sass = require('gulp-sass'),
rename = require('gulp-rename');
const gulp = require('gulp');
const autoprefixer = require('gulp-autoprefixer');
const sass = require('gulp-sass');
const rename = require('gulp-rename');
function sassTask(options) {
// Requires: dupe.
@ -13,9 +11,7 @@ function sassTask(options) {
return options
.src(options.workingDir + '/**/*.scss')
.pipe(sass())
.pipe(
autoprefixer()
)
.pipe(autoprefixer())
.pipe(rename({ extname: '.css' }))
.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;
}
tr{
tr {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
box-sizing: border-box;
font-size: 14px;
line-height: 22px;
}
.main{
width:100%;
.main {
width: 100%;
background-color: #ffffff;
}
.rbcc{
.rbcc {
/*
* rbcc -> reset - border - cellspacing - cellpading
*
* Resets table attributes.
*/
border:0;
cellpadding:0;
cellspacing:0;
border: 0;
cellpadding: 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;
}
.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;
}
a{
a {
text-decoration: none;
color: #0fade1;
}
@ -57,7 +61,7 @@ a{
* 2. Content styles.
* ==================
*/
.main__welcome{
.main__welcome {
color: #000;
padding: 10px 30px 0 30px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
@ -65,93 +69,92 @@ a{
line-height: 22px;
}
.main__content{
.main__content {
color: #000;
padding: 10px 30px 0 30px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px;
}
/*
* ================
* 3. Footer styles.
* ================
*/
.footer{
.footer {
background-color: #303030;
padding: 20px 30px 0px 30px;
color: #f5f5f5;
border-top: 8px solid #585858;
}
.footer a{
.footer a {
color: #f5f5f5;
}
.footer--simple{
.footer--simple {
padding-bottom: 20px;
background-color: #FFFFFF;
background-color: #ffffff;
}
.footer--simple tr td{
.footer--simple tr td {
color: #888;
}
.footer__main{
.footer__main {
/* 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;
color: #f5f5f5;
/* @todo gulp-inline-css doesn't parse align */
align:left;
/* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align: left;
}
.footer__main__col1{
width:70%;
margin-bottom:40px;
/* @todo gulp-inline-css doesn't parse align */
align:left;
.footer__main__col1 {
width: 70%;
margin-bottom: 40px;
/* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align: left;
}
.footer__main__col1__td{
color: #9E9E9E;
/* @todo gulp-inline-css doesn't parse align */
align:left;
.footer__main__col1__td {
color: #9e9e9e;
/* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align: left;
padding-top: 15px;
}
.footer__main__col1__td > span{
font-size:18px;
margin-bottom:5px;
.footer__main__col1__td > span {
font-size: 18px;
margin-bottom: 5px;
}
.footer__main a > span{
.footer__main a > span {
/* Revert apple blue-link style. */
color: #f5f5f5!important;
text-decoration:none!important;
color: #f5f5f5 !important;
text-decoration: none !important;
}
.footer__main__col2{
width:30%;
/* @todo gulp-inline-css doesn't parse align */
align:right;
.footer__main__col2 {
width: 30%;
/* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align: right;
}
.footer__main__col2__td{
.footer__main__col2__td {
font-size: 14px;
color: #f5f5f5;
/* @todo gulp-inline-css doesn't parse align */
align:right;
/* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
align: right;
}
.footer__main__col2__td__img{
.footer__main__col2__td__img {
border: 0;
padding-left:20px;
padding-left: 20px;
max-width: 100%;
max-height:65px;
max-height: 65px;
height: auto;
}
}

View File

@ -1,35 +1,32 @@
<td class="footer">
<table class="rbcc footer__main">
<tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__signature" align="left">
<!-- @echo signature --><br />
<strong><!-- @echo name --></strong><br />
</td>
</tr>
<tr class="spd"><td class="spd__inner"></td></tr>
<tr class="spd">
<td class="spd__inner"></td>
</tr>
<tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<table class="rbcc footer__main__col2" align="right">
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__col2__td" align="right">
<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>
</td>
</table>
<!-- @todo gulp-inline-css doesn't parse align -->
<table class="rbcc footer__main__col1" align="left">
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__col1__td" align="left">
<span><!-- @echo slogan --></span><br/>
<span><!-- @echo slogan --></span><br />
<!-- @echo contactMain -->
<a href="mailto:<!-- @echo contactMail -->" target="_blank"><!-- @echo contactMail --></a>
</td>
</table>
</tr>
</table>
</td>
</td>

View File

@ -1,16 +1,18 @@
<!-- @include head.inc.html -->
<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">
<tr class="sp"><td class="sp__inner"></td></tr>
<tr class="sp">
<td class="sp__inner"></td>
</tr>
<tr class="rbcc">
<td class="footer footer--simple">
<table class="rbcc footer__main">
<tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__signature" align="left">
<!-- @echo signature --><br />
<!-- @echo name --><br />
@ -20,4 +22,4 @@
</td>
</tr>
</table>
</body>
</body>

View File

@ -1,36 +1,32 @@
<td class="footer">
<table class="rbcc footer__main">
<tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__signature" align="left">
<!-- @echo signature --><br />
<!-- @echo name --><br />
</td>
</tr>
<tr class="gray-hr">
<td><hr class="gray-hr"/></td>
<td><hr class="gray-hr" /></td>
</tr>
<tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<table class="rbcc footer__main__col1" align="left">
<td class="footer__main__col1__td" align="left">
<strong>
<!-- @echo contactMain -->
<a href="mailto:<!-- @echo contactMail -->" target="_blank"><!-- @echo contactMail --></a>
</strong><br />
<a href="mailto:<!-- @echo contactMail -->" target="_blank"><!-- @echo contactMail --></a> </strong
><br />
<!-- @echo contactSecondary -->
</td>
</table>
<!-- @todo gulp-inline-css doesn't parse align -->
<table class="rbcc footer__main__col2" align="right">
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__col2__td" align="right">
<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>
</td>
</table>
</tr>
</table>
</td>
</td>

View File

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

View File

@ -1,15 +1,16 @@
<!-- @include head.inc.html -->
<body>
<br/>
<br />
<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">
<td class="footer footer--simple">
<table class="rbcc footer__main">
<tr>
<!-- @todo gulp-inline-css doesn't parse align -->
<td class="footer__main__signature" align="left">
<!-- @echo signature --><br />
<!-- @echo name --><br />
@ -19,4 +20,4 @@
</td>
</tr>
</table>
</body>
</body>

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,46 +0,0 @@
import test from 'ava';
import fs from 'fs';
test.before((t) => {
const { exec } = require('child_process');
exec('gulp', (err, stdout, stderr) => {
if (err) {
console.error('Failed to run gulp', err);
}
});
});
test('dark signature output', async (t) => {
const expected = fs.readFileSync('./tests/sample/dark/signature-dark.html');
const built = fs.readFileSync('./dist/dark/signature-dark.html');
t.deepEqual(expected, built);
});
test('dark signature reply output', async (t) => {
const expected = fs.readFileSync('./tests/sample/dark/signature-reply-dark.html');
const built = fs.readFileSync('./dist/dark/signature-reply-dark.html');
t.deepEqual(expected, built);
});
test('light signature output', async (t) => {
const expected = fs.readFileSync('./tests/sample/light/signature-light.html');
const built = fs.readFileSync('./dist/light/signature-light.html');
t.deepEqual(expected, built);
});
test('light signature reply output', async (t) => {
const expected = fs.readFileSync('./tests/sample/light/signature-reply-light.html');
const built = fs.readFileSync('./dist/light/signature-reply-light.html');
t.deepEqual(expected, built);
});
test('light full mail output', async (t) => {
const expected = fs.readFileSync('./tests/sample/light/full-mail-light.html');
const built = fs.readFileSync('./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')
};