Compare commits
155 Commits
v4.0.0-rc.
...
master
| 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 | |
|
|
238daab855 | |
|
|
29354f0920 | |
|
|
268cbed282 | |
|
|
3c47d1f4bc | |
|
|
bfb19dec08 | |
|
|
5e10fab4c7 | |
|
|
4c10c45222 | |
|
|
6c30066e4d | |
|
|
2f56f93d79 | |
|
|
fad368ecb2 | |
|
|
b969802d87 | |
|
|
1f24c76467 | |
|
|
06badf0964 | |
|
|
e42f190e40 | |
|
|
07e8fb7952 | |
|
|
1bd69ddb88 | |
|
|
f2e268bd9c | |
|
|
d6300db1e6 | |
|
|
a5f0b3e83f | |
|
|
d567e67d7e | |
|
|
ecaf31b42d | |
|
|
dbf0951627 | |
|
|
af88ce036c | |
|
|
3699600c47 | |
|
|
823a7be173 | |
|
|
bae078590a | |
|
|
207737f437 | |
|
|
23ae549b64 | |
|
|
bcb013e39d | |
|
|
f2aa0c2366 | |
|
|
f921b882a0 | |
|
|
95460a4e0c | |
|
|
160263f899 | |
|
|
201737e166 | |
|
|
e9dbb5b37e | |
|
|
a6c2561d1e | |
|
|
9f22e6d4f4 | |
|
|
fe82f7ab16 | |
|
|
fa45c63ce8 | |
|
|
e479d03613 | |
|
|
108747ee1d | |
|
|
aba228edbe | |
|
|
cf2f38358f | |
|
|
1fe90ee748 | |
|
|
9dcf115781 | |
|
|
a33140224b | |
|
|
2bd5f576ee | |
|
|
9b2d698c33 | |
|
|
27af00d9fe | |
|
|
12bffe8169 | |
|
|
5b907d3bf9 | |
|
|
03ab7366de | |
|
|
59804bd735 | |
|
|
cfbfd696f1 | |
|
|
16f00eb247 | |
|
|
090b3f03ed | |
|
|
3348ad9dc1 | |
|
|
cbb3743613 | |
|
|
18606cff77 | |
|
|
f4d1bc65db | |
|
|
b3528c9aff | |
|
|
64371a56af | |
|
|
514ad88dad | |
|
|
c9a6096d10 |
|
|
@ -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
|
||||
|
|
@ -3,3 +3,5 @@
|
|||
/tmp/
|
||||
.DS_Store
|
||||
/dist
|
||||
IDEAS.md
|
||||
npm_debug.log
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
Hi there, please provide some info about your enviornment before submiting this issue:
|
||||
- device :
|
||||
- OS :
|
||||
- email client :
|
||||
- node version :
|
||||
- npm version :
|
||||
- error message :
|
||||
200
README.md
|
|
@ -1,131 +1,243 @@
|
|||
# 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:
|
||||
|
||||
Here are some examples:
|
||||

|
||||

|
||||
|
||||
## Read the docs in other languages
|
||||
[Go here](/i18n). You are welcome to add your own language 😋
|
||||
## Getting started
|
||||
|
||||
- Clone repo `git clone https://github.com/danmindru/responsive-html-email-signature.git`
|
||||
- Run `npm install`
|
||||
- Run `npm start` to generate templates from configuration. This will continue to watch your files and re-make the template until you exit.
|
||||
|
||||
### Customizing templates
|
||||
|
||||
- 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
|
||||
Let's make writing HTML emails & email signatures easier. We won't fix all email clients, but we can surely make our lives easier with some neat automation. <br/>
|
||||
See a fairly comprehensive rant on the subject (and not only) [in this article](https://fadeit.dk/blog/post/html-emails-and-email-signatures-how-hard-can-it-be).
|
||||
|
||||
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 it do
|
||||
- [x] config-based template generation
|
||||
- [x] allows generating multiple templates (for your colleagues too!)
|
||||
## 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] media queries for mail clients that support them
|
||||
- [x] ads some basic media queries for mail clients that support them
|
||||
- [x] can build templates from multiple sources
|
||||
- [x] 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
|
||||
|
||||
## Getting started
|
||||
```bash
|
||||
$ npm install
|
||||
$ gulp # By default, HTML & CSS files in './src' 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.
|
||||
```
|
||||
|
||||
Take a look at `src/light/` for an example. Copy / Paste, rename it and change `src/light/conf.js` to suite your needs. Run `gulp` to build the templates (into `/dist`).
|
||||
> NB: Stylesheets are included automatically, place them wherever in the directory.
|
||||
> 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>",
|
||||
"signature": "<signature-of-choice>",
|
||||
"name": "<your-name>",
|
||||
"contactMain": "<phone-or-email-or-html>",
|
||||
"contactMail": "<email>",
|
||||
"slogan": "<a-basic-slogan>",
|
||||
"logoUrl": "</assets/dark.png?>",
|
||||
"logoAlt": "<text-in-case-logo-is-blocked>",
|
||||
"website": "<http://dark.dk>"
|
||||
}
|
||||
```
|
||||
|
||||
### Generating multiple emails from the same config (for your colleagues too!)
|
||||
|
||||
To generate multiple templates, use an array instead of an object in `conf.json`, like so:
|
||||
|
||||
```json
|
||||
[{ ...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:
|
||||
|
||||
```html
|
||||
<p><!-- @echo yourCustomVariable --></p>
|
||||
```
|
||||
|
||||
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.
|
||||
Each HTML file will be treated as an email template, except for `*.inc.html`. See below ⬇️
|
||||
|
||||
### Using partials (\*.inc.html)
|
||||
|
||||
By naming files with `*.inc.html`, they become partials. Partials will not be treated as templates (ignored), but they can be included in any HTML template using the `@include` HTML comment.
|
||||
|
||||
```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
|
||||
./src
|
||||
./templates
|
||||
├── dark
|
||||
├── conf.js # Template strings, logo, etc.
|
||||
├── assets
|
||||
├── dark.png # Image to embed as base64
|
||||
├── conf.json # Template strings, logo, etc.
|
||||
├── dark.css # Stylesheet.
|
||||
├── footer.inc.html # Contact info & logo
|
||||
├── head.inc.html # 'Responsive' CSS goes here
|
||||
├── signature-reply.inc.html # Simplified signature (loads head)
|
||||
├── signature.html # Full signature (loads head/footer)
|
||||
├── signature-reply.html # Simplified signature (loads head)
|
||||
```
|
||||
|
||||
Here's how the light one looks:
|
||||
Here's how the light one is structured:
|
||||
|
||||
```bash
|
||||
./src
|
||||
./templates
|
||||
├── light
|
||||
├── conf.js # Template strings, logo, etc.
|
||||
├── assets
|
||||
├── light.png # Image to embed as base64
|
||||
├── conf.json # Template strings, logo, etc.
|
||||
├── footer.inc.html # Contact info & logo
|
||||
├── full-mail.html # Body + signature
|
||||
├── head.inc.html # 'Responsive' CSS goes here
|
||||
├── signature-reply.inc.html # Simplified signature (loads head)
|
||||
├── light.css # Stylesheet.
|
||||
├── signature.html # Full signature (loads head/footer)
|
||||
├── 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 'src' 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`.
|
||||

|
||||
|
||||
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
|
||||
|
||||
## Usage with different e-mail 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)//...`.
|
||||
|
||||
### 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.
|
||||
> **NB**: Images won't appear in the signature preview, but will work fine when you compose a message.
|
||||
|
||||
#### Solution 2
|
||||
|
||||
####Solution 2
|
||||
You can also open the HTML files in `/dist` in a browser, CMD + A, CMD + C and then paste into the signature box. This won't copy the `<html>` part or the `<style>` part that includes media queries. Follow the guide if you want it.
|
||||
|
||||
|
||||
#### 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
|
||||
```
|
||||
|
|
@ -135,14 +247,15 @@ 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
|
||||
|
||||
> As the AppData folder is hidden, I'd recommend you to opne it via CMD.
|
||||
> As the AppData folder is hidden, I'd recommend you to open it via CMD.
|
||||
|
||||
```
|
||||
cd AppData\Roamin\Microsoft
|
||||
cd AppData\Roaming\Microsoft
|
||||
start Signatures
|
||||
```
|
||||
|
||||
|
|
@ -151,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
|
||||
|
|
@ -159,10 +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.
|
||||
|
||||
===================
|
||||
<br/>
|
||||
<a href="http:fadeit.dk"><img src="https://fadeit.dk/src/assets/img/brand/fadeit_logo_full.svg" alt="The fadeit logo" style="width:200px;"/></a><br/><br/>
|
||||
## Other commands
|
||||
|
||||
####About fadeit
|
||||
We build awesome software, web and mobile applications.
|
||||
See more at [fadeit.dk](https://fadeit.dk)
|
||||
### `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'
|
||||
}
|
||||
|
|
@ -0,0 +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__main__col1,.footer__main__col2{width:100%!important}.footer td{font-size:12px!important}.footer__main__col1__td{text-align:left}.footer__main__col1__td>span{margin-bottom:10px}.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="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="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="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>
|
||||
|
|
@ -0,0 +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,.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){.background,.body-with-bg{background-color:#F1F1F1}.body-with-bg .main{border:1px solid #E9E9E9!important;max-width:960px;margin:0 auto}.background{padding:30px}}</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="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="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="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>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": "ONEstore",
|
||||
"name": "0 0 0 매니저",
|
||||
"team": "스토어 기획팀",
|
||||
"title": "Dev Relations",
|
||||
"welcome": "안녕하세요.",
|
||||
"introParagraph": "Thanks for writing up this email.<br/> We are delighted to reply with a responsive template.",
|
||||
"contactMain": "<a href='tel:+821012345678'><span>+82-10-1234-5678</span></a> | ",
|
||||
"contactMail": "devhelper@onestore.co.kr",
|
||||
"contactSecondary": "성남시 분당구 판교역로 188 SK플래닛 건물 11층",
|
||||
"logoUrl": "assets/type01.png",
|
||||
"logoAlt": "Onestore logo",
|
||||
"website": "http://onesto.re/"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "ONEstoreBlack",
|
||||
"signature": "Best regards,",
|
||||
"name": "Joo Hyung Park",
|
||||
"team": "Service Planning Team",
|
||||
"title": "Dev Relations",
|
||||
"welcome": "안녕하세요.",
|
||||
"introParagraph": "Thanks for writing up this email.<br/> We are delighted to reply with a responsive template.",
|
||||
"contactMain": "Call <a href='tel:+821012345678'><span>+82-10-1234-5678</span></a> or email us at",
|
||||
"contactMail": "devhelper@onestore.co.kr",
|
||||
"contactSecondary": "188, Pangyoyeok-ro, Bundang-gu, Seongnam-si, Gyeonggi-do, Korea",
|
||||
"logoUrl": "assets/type03.png",
|
||||
"logoAlt": "ONEstore. logo",
|
||||
"website": "http://onesto.re/"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "fadeit",
|
||||
"signature": "Yours truly,",
|
||||
"name": "Jane Whatsmyname",
|
||||
"welcome": "Hi there,",
|
||||
"introParagraph": "Thanks for writing up this email.<br/> We are delighted to reply with a responsive template.",
|
||||
"contactMain": "Call <a href='tel:81100200'><span>81100200</span></a> or email us at",
|
||||
"contactMail": "info@fadeit.dk",
|
||||
"contactSecondary": "Anelystparken 31, DK-8381 Tilst, Aarhus",
|
||||
"logoUrl": "http://fadeit.dk/src/assets/img/brand/fadeit-logo.png",
|
||||
"logoAlt": "fadeit logo",
|
||||
"website": "http://fadeit.dk"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": "play",
|
||||
"signature": "Best regards,",
|
||||
"name": "Jane Whatsmyname",
|
||||
"contactMain": "Call <a href='tel:004581100200'><span>(45) 81100200</span></a> or email us at",
|
||||
"contactMail": "info@tryplay.dk",
|
||||
"slogan": "LED Pylon. LED Wall. Digital Signage.",
|
||||
"logoUrl": "https://informationscreen.com/manage/assets/images/play-logo.png",
|
||||
"logoAlt": "Play. logo",
|
||||
"website": "http://tryplay.dk"
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Responsive HTML emails & emails signature</title>
|
||||
<meta name="description" content="Automate the creation of HTML emails and email signatures. Generate multiple emails for your colleagues, friends or enemies too!">
|
||||
|
||||
<style>
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 12px;
|
||||
background-color: rgb(21, 21, 21);
|
||||
}
|
||||
|
||||
body, h1, h2, h3, h4, h5, h6, p {
|
||||
font-family: -apple-system, BlinkMacSystemFont, Arial, sans-serif;
|
||||
font-weight: 300;
|
||||
color: white;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
h1 { font-size: 3em; }
|
||||
h2 { font-size: 2.2em; }
|
||||
h3 { font-size: 1.8em; margin-bottom: 0.3em; }
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.dark { min-height: 430px; }
|
||||
.light { min-height: 350px; }
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
min-height: 100vh;
|
||||
padding: 5em;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
header a {
|
||||
opacity: 0.7;
|
||||
font-size: 1.2em;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 300;
|
||||
border: 1px solid white;
|
||||
border-radius: 4px;
|
||||
max-width: 280px;
|
||||
margin-top: 2em;
|
||||
padding: 1em 2em;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
line-height: 1.2em;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
header a:hover,
|
||||
header a:focus {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin-top: -60px;
|
||||
display: flex;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
figure svg { height: 60px; width: 40px; }
|
||||
|
||||
footer {
|
||||
padding: 5em;
|
||||
}
|
||||
|
||||
footer a {
|
||||
opacity: 0.7;
|
||||
font-size: 1em;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 300;
|
||||
border: 1px solid white;
|
||||
border-radius: 4px;
|
||||
max-width: 280px;
|
||||
margin-top: 2em;
|
||||
padding: 0.6em 1em;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
line-height: 1.2em;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
footer a:hover,
|
||||
footer a:focus {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 767px) {
|
||||
header {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>HTML emails & email signatures should be easier than this.</h1>
|
||||
<a href="https://github.com/fadeit/responsive-html-email-signature">Get started on Github</a>
|
||||
</header>
|
||||
|
||||
<figure>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path fill="white" d="M349.7,322.2c-3.1-3.1-8-3-11.3,0L264,388.6V104c0-4.4-3.6-8-8-8c-4.4,0-8,3.6-8,8v284.6l-74.4-66.3
|
||||
c-3.4-2.9-8.1-3.2-11.2-0.1c-3.1,3.1-3.3,8.5-0.1,11.4c0,0,87,79.2,88,80s2.8,2.4,5.7,2.4s4.9-1.6,5.7-2.4s88-80,88-80
|
||||
c1.5-1.5,2.3-3.6,2.3-5.7C352,325.8,351.2,323.8,349.7,322.2z"/>
|
||||
</svg>
|
||||
</figure>
|
||||
|
||||
<main>
|
||||
<img src="https://cloud.githubusercontent.com/assets/1515742/10591901/139c4954-76b9-11e5-80f7-5b0ccaf5af81.png" alt="Email / email signature HTML template dark">
|
||||
<img src="https://cloud.githubusercontent.com/assets/1515742/10591900/13889d32-76b9-11e5-8dc0-b89d80189e93.png" alt="Email / email signature HTML template white">
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<h3>HTML emails, please.</h3>
|
||||
<p>
|
||||
Grab the code on Github to simplify how HTML emails are built.
|
||||
</p>
|
||||
<a href="https://github.com/fadeit/responsive-html-email-signature">Get started on Github</a>
|
||||
</footer>
|
||||
|
||||
<!-- can do demos later! -->
|
||||
<!-- <iframe class="dark" src="./dist/dark/signature-dark.html"></iframe>
|
||||
<iframe class="light" src="./dist/light/signature-light.html"></iframe> -->
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.27/webfontloader.js"></script>
|
||||
<script>
|
||||
WebFontLoader.load({
|
||||
active () {
|
||||
const body = document.querySelector('body')
|
||||
if (body) body.style.fontFamily = 'Roboto, -apple-system, BlinkMacSystemFont, Arial, sans-serif'
|
||||
}
|
||||
google: {
|
||||
families: ['Roboto:300,400']
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"persons": [
|
||||
{
|
||||
"id": "ONEstore",
|
||||
"name": "0 0 0 매니저",
|
||||
"team": "스토어 기획팀",
|
||||
"title": "Dev Relations",
|
||||
"welcome": "안녕하세요.",
|
||||
"introParagraph": "Thanks for writing up this email.<br/> We are delighted to reply with a responsive template.",
|
||||
"contactMain": "<a href='tel:+821012345678'><span>+82-10-1234-5678</span></a> | ",
|
||||
"contactMail": "devhelper@onestore.co.kr",
|
||||
"contactSecondary": "성남시 분당구 판교역로 188 SK플래닛 건물 11층",
|
||||
"logoUrl": "assets/type01.png",
|
||||
"logoAlt": "Onestore logo",
|
||||
"website": "http://onesto.re/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"persons": [
|
||||
{
|
||||
"id": "ONEstoreBlack",
|
||||
"signature": "Best regards,",
|
||||
"name": "Joo Hyung Park",
|
||||
"team": "Service Planning Team",
|
||||
"title": "Dev Relations",
|
||||
"welcome": "안녕하세요.",
|
||||
"introParagraph": "Thanks for writing up this email.<br/> We are delighted to reply with a responsive template.",
|
||||
"contactMain": "Call <a href='tel:+821012345678'><span>+82-10-1234-5678</span></a> or email us at",
|
||||
"contactMail": "devhelper@onestore.co.kr",
|
||||
"contactSecondary": "188, Pangyoyeok-ro, Bundang-gu, Seongnam-si, Gyeonggi-do, Korea",
|
||||
"logoUrl": "assets/type03.png",
|
||||
"logoAlt": "ONEstore. logo",
|
||||
"website": "http://onesto.re/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"persons": [
|
||||
{
|
||||
"id": "fadeit",
|
||||
"signature": "Yours truly,",
|
||||
"name": "Jane Whatsmyname",
|
||||
"welcome": "Hi there,",
|
||||
"introParagraph": "Thanks for writing up this email.<br/> We are delighted to reply with a responsive template.",
|
||||
"contactMain": "Call <a href='tel:81100200'><span>81100200</span></a> or email us at",
|
||||
"contactMail": "info@fadeit.dk",
|
||||
"contactSecondary": "Anelystparken 31, DK-8381 Tilst, Aarhus",
|
||||
"logoUrl": "http://fadeit.dk/src/assets/img/brand/fadeit-logo.png",
|
||||
"logoAlt": "fadeit logo",
|
||||
"website": "http://fadeit.dk"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"persons": [
|
||||
{
|
||||
"id": "play",
|
||||
"signature": "Best regards,",
|
||||
"name": "Jane Whatsmyname",
|
||||
"contactMain": "Call <a href='tel:004581100200'><span>(45) 81100200</span></a> or email us at",
|
||||
"contactMail": "info@tryplay.dk",
|
||||
"slogan": "LED Pylon. LED Wall. Digital Signage.",
|
||||
"logoUrl": "https://informationscreen.com/manage/assets/images/play-logo.png",
|
||||
"logoAlt": "Play. logo",
|
||||
"website": "http://tryplay.dk"
|
||||
}
|
||||
]
|
||||
}
|
||||
64
gulpfile.js
|
|
@ -1,30 +1,50 @@
|
|||
'use strict';
|
||||
const gulp = require('gulp');
|
||||
const plumber = require('gulp-plumber');
|
||||
|
||||
var gulp = require('gulp'),
|
||||
fs = require('fs'),
|
||||
plumber = require('gulp-plumber');
|
||||
const { SOURCE_DIR, DIST_DIR, WORKING_DIR, CONFIGURATION_FILE } = require('./constants');
|
||||
|
||||
var options = {
|
||||
source: 'src',
|
||||
dist: 'dist',
|
||||
workingDir: 'tmp',
|
||||
src: function plumbedSrc(){
|
||||
const options = {
|
||||
sourceDir: SOURCE_DIR,
|
||||
distDir: DIST_DIR,
|
||||
workingDir: WORKING_DIR,
|
||||
configurationFile: CONFIGURATION_FILE,
|
||||
src: function plumbedSrc() {
|
||||
return gulp.src.apply(gulp, arguments).pipe(plumber());
|
||||
}
|
||||
};
|
||||
|
||||
/** Load tasks from the '/tasks' directory.
|
||||
* Look for .js & .coffee files.
|
||||
* Each file should correspond to a task.
|
||||
/**
|
||||
* Load tasks from the '/tasks' directory.
|
||||
*/
|
||||
fs
|
||||
.readdirSync('./tasks')
|
||||
.filter(function readJSFiles(file) {
|
||||
return (/\.(js|coffee)$/i).test(file);
|
||||
})
|
||||
.map(function loadTasks(file) {
|
||||
require('./tasks/' + file)(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', then gulp will watch for changes in '/src'. */
|
||||
gulp.task('default', ['dupe', 'less', 'sass', 'postcss', 'lint', 'build', 'watch']);
|
||||
/* 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('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')
|
||||
);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
#Translated docs
|
||||
# Translated docs
|
||||
|
||||
The [main README](https://github.com/fadeit/responsive-html-email-signature) is always going to be the most up-to-date, so if something doesn't add up, take a look there.
|
||||
|
||||
*Available translations are:*
|
||||
- [Korean](ko-KR.md) by [JooHyung Park](https://github.com/dusskapark)
|
||||
<!-- SOON! - [Romanian](ro-RO.md) by [Dan Mindru](https://github.com/danmindru) -->
|
||||
|
||||
##Contributing
|
||||
## Contributing
|
||||
You're very welcome to contribute & maintain translations.
|
||||
Mini-guide:
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@
|
|||
## 만들게된 동기 (Motivation)
|
||||
이메일용 html은 일반적인 HTML과 달리 모든 코드를 in-line CSS로 만들어야 합니다. 그래서 이메일 템플릿이랑 서명을 만들기가 굉장히 어렵습니다. "이런 HTML 템플릿과 서명을 편하게 만들어보자!" 라는 생각에서 이 템플릿을 만들었습니다. 물론 모든 이메일 클라이언트의 이슈를 전부 다 고칠 수는 없지만, 이 템플릿을 활용해서 조금 더 쉽고 깔끔한 반응형 이메일을 만들어서 받는 사람 보내는 사람 모두가 만족하게 만들 수 있습니다!
|
||||
|
||||
이 [블로그 포스팅](http://fadeit.dk/blog/post/html-emails-and-email-signatures-how-hard-can-it-be)에서 fadeit 의 더 많은 이애기를 들어보세요.
|
||||
|
||||
|
||||
## 이 템플릿에서 무엇을 할 수 있나? (What does it do?)
|
||||
- [x] config-based template generation
|
||||
- [x] allows generating multiple templates (for your colleagues too!)
|
||||
|
|
@ -38,7 +35,7 @@ $ npm install
|
|||
$ gulp
|
||||
```
|
||||
|
||||
`src/fadeit/` 를 확인해보시면 멋진 2개의 이메일 템플릿 샘플이 있습니다. 폴더를 통째로 다른 이름으로 복사/붙여넣기 하시고 그 안에 있는 `src/fadeit/conf.js` 파일을 입맛대로 바꿔보세요. 다음으로는 `gulp`를 실행해서 여러분 만의 이메일 템플릿을 빌드하세요. gulp 의 task가 동작하면서 기본 html CSS 파일을 확인하고 새로운 html 파일을 `/dist`에 저장합니다.
|
||||
`src/dark/` 를 확인해보시면 멋진 2개의 이메일 템플릿 샘플이 있습니다. 폴더를 통째로 다른 이름으로 복사/붙여넣기 하시고 그 안에 있는 `src/dark/conf.js` 파일을 입맛대로 바꿔보세요. 다음으로는 `gulp`를 실행해서 여러분 만의 이메일 템플릿을 빌드하세요. gulp 의 task가 동작하면서 기본 html CSS 파일을 확인하고 새로운 html 파일을 `/dist`에 저장합니다.
|
||||
|
||||
## 살펴보기 (Overview)
|
||||
아래 순서도는 여러분이 만든 템플릿이 어떻게 빌드가 되는지를 보여줍니다.
|
||||
|
|
@ -131,12 +128,3 @@ start Signatures
|
|||
- 새 서명을 만들고 아래 박스에 Ctrl + V 로 파일을 붙여넣기 하세요.
|
||||
|
||||
> base 64로 만든 이메일 파일은 잘 작동하지 않을 수 있습니다. 외부 링크를 활용하시기를 권장합니다.
|
||||
|
||||
|
||||
===================
|
||||
<br/>
|
||||
<a href="http:fadeit.dk"><img src="http://fadeit.dk/src/assets/img/brand/fadeit_logo_full.svg" alt="The fadeit logo" style="width:200px;"/></a><br/><br/>
|
||||
|
||||
####About fadeit
|
||||
We build awesome software, web and mobile applications.
|
||||
See more at [fadeit.dk](http://fadeit.dk)
|
||||
|
|
|
|||
103
package.json
|
|
@ -1,45 +1,94 @@
|
|||
{
|
||||
"name": "responsive-html-email-signature",
|
||||
"version": "4.0.0-rc.2",
|
||||
"description": "Responsive template for email signatures",
|
||||
"version": "6.1.0",
|
||||
"description": "Responsive template for emails & email signatures.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/fadeit/responsive-html-email-signature.git"
|
||||
"url": "git+https://github.com/danmindru/responsive-html-email-signature.git"
|
||||
},
|
||||
"keywords": [
|
||||
"responsive",
|
||||
"template",
|
||||
"email",
|
||||
"signature"
|
||||
"signature",
|
||||
"email-signatures",
|
||||
"inline-styles",
|
||||
"watches-html"
|
||||
],
|
||||
"author": "Dan Mindru <mindrudan@gmail.com> (http://mindrudan.com/)",
|
||||
"author": "Dan Mindru <mindrudan@gmail.com> (https://mindrudan.com/)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/fadeit/responsive-html-email-signature/issues"
|
||||
"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"
|
||||
},
|
||||
"homepage": "https://github.com/fadeit/responsive-html-email-signature#readme",
|
||||
"dependencies": {
|
||||
"autoprefixer": "^6.3.6",
|
||||
"del": "^2.2.0",
|
||||
"fs-extra": "^0.30.0",
|
||||
"gulp": "~3.9.1",
|
||||
"gulp-autoprefixer": "^3.1.0",
|
||||
"gulp-david": "~0.4.2",
|
||||
"gulp-inline-css": "~3.1.0",
|
||||
"gulp-inline-image-html": "~0.2.1",
|
||||
"gulp-jsonlint": "^1.1.2",
|
||||
"gulp-less": "^3.1.0",
|
||||
"autoprefixer": "^9.6.1",
|
||||
"chalk": "^2.4.2",
|
||||
"cheerio": "^0.22.0",
|
||||
"del": "^5.1.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-autoprefixer": "^7.0.1",
|
||||
"gulp-david": "^1.0.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": "~0.2.1",
|
||||
"gulp-plumber": "^1.1.0",
|
||||
"gulp-postcss": "^6.1.1",
|
||||
"gulp-preprocess": "~2.0.0",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-sass": "^2.3.1",
|
||||
"q": "~1.4.1"
|
||||
"gulp-minify-inline": "^1.1.0",
|
||||
"gulp-plumber": "^1.2.1",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-preprocess": "^3.0.3",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-sass": "^4.1.0",
|
||||
"klaw": "^3.0.0",
|
||||
"node-sass": "^7.0.0",
|
||||
"plugin-error": "^1.0.1",
|
||||
"through2": "^2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
* =====================================
|
||||
* 1. Common styles for general table things.
|
||||
* =====================================
|
||||
*/
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
tr{
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.rbcc{
|
||||
/*
|
||||
* rbcc -> reset - border - cellspacing - cellpading
|
||||
*
|
||||
* Resets table attributes.
|
||||
*/
|
||||
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{
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* =================
|
||||
* 2. Content styles.
|
||||
* ==================
|
||||
*/
|
||||
.main__welcome{
|
||||
color: #000;
|
||||
padding: 10px 30px 0 30px;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.main__content{
|
||||
color: #000;
|
||||
padding: 10px 30px 0 30px;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ================
|
||||
* 3. Footer styles.
|
||||
* ================
|
||||
*/
|
||||
.footer{
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px 30px 0px 30px;
|
||||
color: #888;
|
||||
border-top: 8px solid #EAEAEA;
|
||||
}
|
||||
|
||||
.footer a{
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.footer--simple{
|
||||
padding-bottom: 20px;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.footer__main{
|
||||
/* NB: This prop fucks up the width on OS X, needs to be *JUST* attribute. */
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.footer__main__signature{
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
/* @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 */
|
||||
align:left;
|
||||
}
|
||||
|
||||
.footer__main__col1__td{
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
/* @todo gulp-inline-css doesn't parse align */
|
||||
align:left;
|
||||
}
|
||||
|
||||
.footer__main a > span{
|
||||
/* Revert apple blue-link style. */
|
||||
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__td{
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
/* @todo gulp-inline-css doesn't parse align */
|
||||
align:right;
|
||||
}
|
||||
|
||||
.footer__main__col2__td__img{
|
||||
border: 0;
|
||||
padding-top: 6px;
|
||||
padding-left:10px;
|
||||
max-width: 100%;
|
||||
max-height:38px;
|
||||
height: auto;
|
||||
}
|
||||
118
tasks/build.js
|
|
@ -1,102 +1,76 @@
|
|||
'use strict';
|
||||
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');
|
||||
|
||||
var 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'),
|
||||
fsx = require('fs-extra'),
|
||||
fs = require('fs'),
|
||||
Q = require('q'),
|
||||
del = require('del'),
|
||||
jsonlint = require('jsonlint'),
|
||||
inlineimg = require('gulp-inline-image-html');
|
||||
|
||||
function buildTask(options){
|
||||
gulp.task('build', ['dupe', 'less', 'sass', 'postcss', 'lint'], function build() {
|
||||
/** Makes templates for a given directory & its configurations.
|
||||
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 = [];
|
||||
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>.
|
||||
*/
|
||||
fsx
|
||||
.walk(cwd)
|
||||
.on('readable', function walkTemplateDir() {
|
||||
var stylesheet;
|
||||
const files = await getFilePathsForDir(cwd);
|
||||
const context = Object.assign(conf, {
|
||||
stylesheets: getCssLinkTagsFromFilelist(files)
|
||||
});
|
||||
|
||||
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){
|
||||
return prev += '<link rel="stylesheet" href="' + current + '">';
|
||||
}, '');
|
||||
|
||||
options
|
||||
return options
|
||||
.src([cwd + '/**/*.html', '!' + cwd + '/**/*.inc.html'])
|
||||
.pipe(preprocess({
|
||||
context: conf
|
||||
}))
|
||||
.pipe(inlineimg(cwd))
|
||||
.pipe(inlineCss({
|
||||
.pipe(preprocess({ context }))
|
||||
.pipe(inlineImg({ getHTTP: confItems[0]['inlineRemoteUrl'] }))
|
||||
.pipe(
|
||||
inlineCss({
|
||||
applyTableAttributes: true,
|
||||
applyWidthAttributes: true,
|
||||
preserveMediaQueries: true,
|
||||
removeStyleTags: false
|
||||
}))
|
||||
.pipe(minifyHTML({quotes: true}))
|
||||
})
|
||||
)
|
||||
.pipe(minifyHTML({ quotes: true }))
|
||||
.pipe(minifyInline())
|
||||
.pipe(rename(function rename(path){
|
||||
.pipe(
|
||||
rename(function rename(path) {
|
||||
path.dirname = dir;
|
||||
path.basename += '-' + conf.id;
|
||||
return path;
|
||||
}))
|
||||
.pipe(gulp.dest(options.dist));
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(options.distDir));
|
||||
});
|
||||
}
|
||||
|
||||
/** Clean up & then read 'src' to generate templates (build entry point). */
|
||||
del(options.dist)
|
||||
.then(function buildStart(){
|
||||
/*
|
||||
* 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, make a call to the task (`cb`) to let gulp know we're done.
|
||||
* Promisify all 'makeTemplate' calls and when resolved, 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;
|
||||
|
||||
delete require.cache[require.resolve(confPath)];
|
||||
current = require(confPath);
|
||||
promises.push(makeTemplates(dir, current.persons || [current]));
|
||||
});
|
||||
|
||||
Q.all(promises);
|
||||
});
|
||||
const configs = getConfigsForDir(options.workingDir, options.configurationFile);
|
||||
return Promise.all(configs.map(({ dir, confItems }) => makeTemplates(dir, confItems)));
|
||||
})
|
||||
.then(() => done())
|
||||
.catch(err => console.log(err));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var 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');
|
||||
|
||||
var 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(['src/**/*'])
|
||||
.pipe(gulp.dest('./' + options.workingDir));
|
||||
return options.src([options.sourceDir + '/**/*']).pipe(gulp.dest('./' + options.workingDir));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
'use strict';
|
||||
const gulp = require('gulp');
|
||||
const less = require('gulp-less');
|
||||
const autoprefixer = require('gulp-autoprefixer');
|
||||
const rename = require('gulp-rename');
|
||||
|
||||
var gulp = require('gulp'),
|
||||
less = require('gulp-less'),
|
||||
autoprefixer = require('gulp-autoprefixer'),
|
||||
rename = require('gulp-rename');
|
||||
|
||||
function lessTask(options){
|
||||
gulp.task('less', ['dupe'], function(){
|
||||
function lessTask(options) {
|
||||
// Requires: dupe.
|
||||
gulp.task('less', function() {
|
||||
return options
|
||||
.src(options.workingDir + '/**/*.less')
|
||||
.pipe(less())
|
||||
.pipe(autoprefixer({
|
||||
browsers: ['last 5 versions']
|
||||
}))
|
||||
.pipe(autoprefixer())
|
||||
.pipe(rename({ extname: '.css' }))
|
||||
.pipe(gulp.dest(options.workingDir));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
'use strict';
|
||||
const gulp = require('gulp');
|
||||
const jsonlint = require('gulp-jsonlint');
|
||||
|
||||
var gulp = require('gulp'),
|
||||
jsonlint = require("gulp-jsonlint");
|
||||
|
||||
function lintTask(options){
|
||||
gulp.task('lint', ['dupe'], function(){
|
||||
function lintTask(options) {
|
||||
// Requiers: dupe.
|
||||
gulp.task('lint', function() {
|
||||
return options
|
||||
.src(options.workingDir + '/**/conf.json')
|
||||
.pipe(jsonlint())
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
'use strict';
|
||||
const gulp = require('gulp');
|
||||
const postcss = require('gulp-postcss');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
|
||||
var gulp = require('gulp'),
|
||||
postcss = require('gulp-postcss'),
|
||||
autoprefixer = require('autoprefixer');
|
||||
|
||||
function postcssTask(options){
|
||||
gulp.task('postcss', ['dupe'], function () {
|
||||
var processors = [
|
||||
autoprefixer({
|
||||
browsers: ['last 5 versions']
|
||||
})
|
||||
];
|
||||
function postcssTask(options) {
|
||||
// Requires: dupe.
|
||||
gulp.task('postcss', function() {
|
||||
var processors = [autoprefixer()];
|
||||
|
||||
return options
|
||||
.src(options.workingDir + '/**/*.css')
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
'use strict';
|
||||
const gulp = require('gulp');
|
||||
const autoprefixer = require('gulp-autoprefixer');
|
||||
const sass = require('gulp-sass');
|
||||
const rename = require('gulp-rename');
|
||||
|
||||
var gulp = require('gulp'),
|
||||
autoprefixer = require('gulp-autoprefixer'),
|
||||
sass = require('gulp-sass'),
|
||||
rename = require('gulp-rename');
|
||||
|
||||
function sassTask(options){
|
||||
gulp.task('sass', ['dupe'], function(){
|
||||
function sassTask(options) {
|
||||
// Requires: dupe.
|
||||
gulp.task(
|
||||
'sass',
|
||||
gulp.series('dupe', function() {
|
||||
return options
|
||||
.src(options.workingDir + '/**/*.scss')
|
||||
.pipe(sass())
|
||||
.pipe(autoprefixer({
|
||||
browsers: ['last 5 versions']
|
||||
}))
|
||||
.pipe(autoprefixer())
|
||||
.pipe(rename({ extname: '.css' }))
|
||||
.pipe(gulp.dest(options.workingDir));
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = sassTask;
|
||||
|
|
@ -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;
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var gulp = require('gulp');
|
||||
|
||||
function watchTask(options){
|
||||
gulp.task('watch', ['dupe', 'build'], function(){
|
||||
gulp.watch(
|
||||
[
|
||||
options.source + '/**/*.html',
|
||||
options.source + '/**/*.css',
|
||||
options.source + '/**/*.scss',
|
||||
options.source + '/**/*.less',
|
||||
options.source + '/**/conf.js'
|
||||
],
|
||||
['dupe', 'less', 'sass', 'postcss', 'build']
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = watchTask;
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
|
@ -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,31 +1,28 @@
|
|||
<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>
|
||||
|
|
@ -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 />
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
|
@ -1,33 +1,29 @@
|
|||
<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>
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* =====================================
|
||||
* 1. Common styles for general table things.
|
||||
* =====================================
|
||||
*/
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
tr {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.rbcc {
|
||||
/*
|
||||
* rbcc -> reset - border - cellspacing - cellpading
|
||||
*
|
||||
* Resets table attributes.
|
||||
*/
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* =================
|
||||
* 2. Content styles.
|
||||
* ==================
|
||||
*/
|
||||
.main__welcome {
|
||||
color: #000;
|
||||
padding: 10px 30px 0 30px;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.main__content {
|
||||
color: #000;
|
||||
padding: 10px 30px 0 30px;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/*
|
||||
* ================
|
||||
* 3. Footer styles.
|
||||
* ================
|
||||
*/
|
||||
.footer {
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px 30px 0px 30px;
|
||||
color: #888;
|
||||
border-top: 8px solid #eaeaea;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.footer--simple {
|
||||
padding-bottom: 20px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.footer__main {
|
||||
/* NB: This prop fucks up the width on OS X, needs to be *JUST* attribute. */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer__main__signature {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
/* @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; it needs to be duplicated in the HTML template */
|
||||
align: left;
|
||||
}
|
||||
|
||||
.footer__main__col1__td {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
/* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
|
||||
align: left;
|
||||
}
|
||||
|
||||
.footer__main a > span {
|
||||
/* Revert apple blue-link style. */
|
||||
color: #888 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.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 {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
/* @todo gulp-inline-css doesn't parse align; it needs to be duplicated in the HTML template */
|
||||
align: right;
|
||||
}
|
||||
|
||||
.footer__main__col2__td__img {
|
||||
border: 0;
|
||||
padding-top: 6px;
|
||||
padding-left: 10px;
|
||||
max-width: 100%;
|
||||
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 />
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -0,0 +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>
|
||||
|
|
@ -0,0 +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>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
const fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
readFileSync: path => fs.readFileSync(('./', path), 'utf8')
|
||||
};
|
||||