Compare commits
91 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
fa285fc598 | |
|
|
8fb1037afa | |
|
|
8b371bb49c | |
|
|
e721efe2ca | |
|
|
96e868bdf2 | |
|
|
d7baeec5ab | |
|
|
0c1ff6d3dc | |
|
|
6bc6ab5c9d | |
|
|
b9ef0953de | |
|
|
5f68bf303a | |
|
|
5b2710d3c2 | |
|
|
deb81dd037 | |
|
|
f57fd62ce9 | |
|
|
1c27a30ab4 | |
|
|
83c3389945 | |
|
|
aeaadaae53 | |
|
|
51857f1129 | |
|
|
ba355c2c78 | |
|
|
6da4298b1f | |
|
|
054c18687e | |
|
|
6955a0b796 | |
|
|
a0c8b69119 | |
|
|
c0c64b3449 | |
|
|
12ea9b461c | |
|
|
468418fb17 | |
|
|
3f3e92fe29 | |
|
|
12facfd8c2 | |
|
|
6113d1b575 | |
|
|
9610d244dd | |
|
|
f298f8bd85 | |
|
|
b3e0c77988 | |
|
|
1ad1ef9106 | |
|
|
d55786f744 | |
|
|
e599bef72e | |
|
|
8ba0323aaa | |
|
|
86944f3cee | |
|
|
e44a52c89f | |
|
|
8bcabaa10f | |
|
|
d45a069c89 | |
|
|
ab54d1e3e6 | |
|
|
a04d8a225a | |
|
|
7cd7e53300 | |
|
|
6ab7b8d962 | |
|
|
d950d1d265 | |
|
|
4adabe4ecd | |
|
|
77953b721a | |
|
|
f68b5104ce | |
|
|
3c22a3921c | |
|
|
fedff3d67c | |
|
|
abecdb6371 | |
|
|
31f824b664 | |
|
|
6f05af60ad | |
|
|
0150856d77 | |
|
|
1c6ff143b1 | |
|
|
5fcbf1ca19 | |
|
|
6ecb318ca6 | |
|
|
455999b910 | |
|
|
4618b491d7 | |
|
|
d1d656d375 | |
|
|
be239820d1 | |
|
|
46011d12ea | |
|
|
0edb498614 | |
|
|
51702a2c0f | |
|
|
2a43f4309d | |
|
|
9e1780eb08 | |
|
|
8ecbee58c0 | |
|
|
54ba657926 | |
|
|
9c2e12104c | |
|
|
0b869c8359 | |
|
|
3f6dc14c78 | |
|
|
c177e1f24f | |
|
|
265f955af5 | |
|
|
5bac55caac | |
|
|
1dece49925 | |
|
|
1d00da17ff | |
|
|
356ddd41cb | |
|
|
f8687ca6d9 | |
|
|
80160e8135 | |
|
|
b316aa6caa | |
|
|
558ccc2a9e | |
|
|
2970388579 | |
|
|
53226eec93 | |
|
|
06f7ac73fe | |
|
|
ead960958b | |
|
|
41f6dced8f | |
|
|
d8baffdca4 | |
|
|
d9d966c1f7 | |
|
|
dd9f8ef381 | |
|
|
ad525373b7 | |
|
|
284d97efa5 | |
|
|
bbd274c7f7 |
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
demo/* linguist-vendored
|
||||
tests/sample/* linguist-vendored
|
||||
|
|
@ -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
|
||||
|
|
@ -2,4 +2,6 @@
|
|||
/node_modules/
|
||||
/tmp/
|
||||
.DS_Store
|
||||
/dist
|
||||
/dist
|
||||
IDEAS.md
|
||||
npm_debug.log
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
111
README.md
111
README.md
|
|
@ -1,66 +1,73 @@
|
|||
# Responsive HTML email signature(s)
|
||||
|
||||
[](https://www.npmjs.com/package/responsive-html-email-signature)
|
||||
[](/LICENSE)
|
||||
|
||||
[](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:
|
||||

|
||||

|
||||
|
||||
## 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`.
|
||||

|
||||
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
SOURCE_DIR: 'templates',
|
||||
DIST_DIR: 'dist',
|
||||
WORKING_DIR: 'tmp',
|
||||
CONFIGURATION_FILE: 'conf.json'
|
||||
}
|
||||
67
gulpfile.js
67
gulpfile.js
|
|
@ -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')
|
||||
);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
77
package.json
77
package.json
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
178
tasks/build.js
178
tasks/build.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
const gulp = require('gulp');
|
||||
|
||||
function checkForUnusedTask(options) {
|
||||
gulp.task('check-for-missing', async done => {
|
||||
// TODO
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = checkForUnusedTask;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
readFileSync: path => fs.readFileSync(('./', path), 'utf8')
|
||||
};
|
||||
Loading…
Reference in New Issue