├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .npmignore ├── Jenkinsfile ├── README.md ├── docs ├── CNAME ├── _config.yml ├── _includes │ ├── footer.html │ ├── head.html │ ├── header.html │ ├── icon-github.html │ ├── icon-github.svg │ ├── icon-twitter.html │ └── icon-twitter.svg ├── _layouts │ ├── default.html │ ├── page.html │ └── post.html ├── _sass │ ├── _base.scss │ ├── _layout.scss │ └── _syntax-highlighting.scss ├── css │ ├── colors.css │ ├── components.css │ ├── defaults.css │ ├── elements.css │ ├── images.css │ ├── img │ │ └── socials.png │ ├── layout.css │ ├── main.scss │ └── spacings.css ├── documentation.md ├── feed.xml ├── img │ ├── logo.png │ └── socials.png ├── index.html └── js │ └── textversion.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── spec └── textversionsSpec.js └── src └── textversion.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | 10 | [package.json] 11 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | junitresults.xml 4 | docs/_site 5 | docs/.sass-cache -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowDanglingUnderscores":true, 3 | "disallowEmptyBlocks":true, 4 | "disallowMixedSpacesAndTabs":true, 5 | "disallowMultiLineTernary":true, 6 | "disallowMultipleLineStrings":true, 7 | "disallowMultipleSpaces":true, 8 | "disallowNestedTernaries":true, 9 | "disallowNewlineBeforeBlockStatements":true, 10 | "disallowSpacesInCallExpression":true, 11 | "disallowSpacesInAnonymousFunctionExpression":{ 12 | "beforeOpeningRoundBrace":true 13 | }, 14 | "disallowSpaceBeforeSemicolon":true, 15 | "disallowSpaceBeforeComma":true, 16 | "disallowSpaceAfterPrefixUnaryOperators":[ 17 | "+", 18 | "-", 19 | "~", 20 | "!" 21 | ], 22 | "disallowSpaceAfterObjectKeys":true, 23 | "disallowSpacesInsideParenthesizedExpression":true, 24 | "disallowSpacesInsideParentheses":true, 25 | "disallowSpacesInsideObjectBrackets":true, 26 | "disallowSpacesInsideBrackets":true, 27 | "disallowSpacesInsideArrayBrackets":"all", 28 | "disallowSpacesInNamedFunctionExpression":{ 29 | "beforeOpeningRoundBrace":true 30 | }, 31 | "disallowSpacesInFunction":{ 32 | "beforeOpeningRoundBrace":true 33 | }, 34 | "disallowSpacesInFunctionExpression":{ 35 | "beforeOpeningRoundBrace":true 36 | }, 37 | "disallowSpacesInFunctionDeclaration":{ 38 | "beforeOpeningRoundBrace":true 39 | }, 40 | "requireEarlyReturn":true, 41 | "requireCurlyBraces":true, 42 | "requireCommaBeforeLineBreak":true, 43 | "requireCapitalizedConstructors":true, 44 | "requireBlocksOnNewline":true, 45 | "requireCamelCaseOrUpperCaseIdentifiers":true, 46 | "requireAlignedMultilineParams":true, 47 | "disallowUnusedParams":true, 48 | "disallowTrailingWhitespace":true, 49 | "disallowTrailingComma":true, 50 | "requireLineBreakAfterVariableAssignment":true, 51 | "requireSpacesInNamedFunctionExpression":{ 52 | "beforeOpeningCurlyBrace":true 53 | }, 54 | "requireSpacesInGenerator":{ 55 | "afterStar":true 56 | }, 57 | "requireSpacesInFunction":{ 58 | "beforeOpeningCurlyBrace":true 59 | }, 60 | "requireSpacesInFunctionExpression":{ 61 | "beforeOpeningCurlyBrace":true 62 | }, 63 | "requireSpacesInFunctionDeclaration":{ 64 | "beforeOpeningCurlyBrace":true 65 | }, 66 | "requireSpacesInForStatement":true, 67 | "requireSpacesInConditionalExpression":{ 68 | "afterTest":true, 69 | "beforeConsequent":true, 70 | "afterConsequent":true, 71 | "beforeAlternate":true 72 | }, 73 | "requireSpacesInAnonymousFunctionExpression":{ 74 | "beforeOpeningCurlyBrace":true 75 | }, 76 | "requireSpaceBetweenArguments":true, 77 | "requireSpaceBeforeObjectValues":true, 78 | "requireSpaceBeforeKeywords":[ 79 | "else", 80 | "while", 81 | "catch" 82 | ], 83 | "requireSpaceBeforeBlockStatements":1, 84 | "requireSpaceBeforeBinaryOperators":true, 85 | "requireSpaceAfterKeywords":[ 86 | "do", 87 | "for", 88 | "if", 89 | "else", 90 | "switch", 91 | "case", 92 | "try", 93 | "catch", 94 | "void", 95 | "while", 96 | "with", 97 | "return", 98 | "typeof" 99 | ], 100 | "requireSpaceAfterComma":true, 101 | "requireSpaceAfterBinaryOperators":true, 102 | "requireSemicolons":true, 103 | "requireParenthesesAroundIIFE":true, 104 | "requirePaddingNewLinesInObjects":true, 105 | "requirePaddingNewLinesBeforeExport":true, 106 | "requirePaddingNewLinesAfterUseStrict":true, 107 | "requirePaddingNewLineAfterVariableDeclaration":true, 108 | "requireObjectKeysOnNewLine":true, 109 | "requireLineFeedAtFileEnd":true, 110 | "validateIndentation":"\t", 111 | "validateLineBreaks":"LF", 112 | "validateParameterSeparator":", ", 113 | "validateQuoteMarks":"\"" 114 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi":false, 3 | "boss":false, 4 | "debug":false, 5 | "evil":false, 6 | "bitwise":true, 7 | "browser":true, 8 | "browserify":true, 9 | "curly":true, 10 | "eqeqeq":true, 11 | "funcscope":true, 12 | "jasmine":true, 13 | "mocha":true, 14 | "maxcomplexity":32, 15 | "maxdepth":12, 16 | "maxparams":12, 17 | "nocomma":true, 18 | "nonbsp":true, 19 | "nonew":true, 20 | "notypeof":false, 21 | "node":true, 22 | "esnext":true, 23 | "globals":{ 24 | "jquery": true, 25 | "$": true 26 | }, 27 | "globalstrict":false, 28 | "immed":true, 29 | "newcap":true, 30 | "plusplus":true, 31 | "predef":[ 32 | "$" 33 | ], 34 | "phantom":true, 35 | "shadow":true, 36 | "singleGroups":false, 37 | "quotmark":"double", 38 | "strict":false, 39 | "undef":true, 40 | "unused":true, 41 | "validthis":true, 42 | "withstmt":false 43 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # node-waf configuration 19 | .lock-wscript 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directories 25 | node_modules 26 | jspm_packages 27 | 28 | # Optional npm cache directory 29 | .npm 30 | 31 | # Optional REPL history 32 | .node_repl_history 33 | 34 | ### SublimeText ### 35 | # cache files for sublime text 36 | *.tmlanguage.cache 37 | *.tmPreferences.cache 38 | *.stTheme.cache 39 | 40 | # workspace files are user-specific 41 | *.sublime-workspace 42 | 43 | # project files should be checked into the repository, unless a significant 44 | # proportion of contributors will probably not be using SublimeText 45 | *.sublime-project 46 | 47 | **/*.build.js -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | tools { 4 | nodejs 'Node Argon [4.6.0] + mocha, gulp, grunt, jasmine' 5 | } 6 | stages { 7 | stage('build') { 8 | steps { 9 | sh 'npm install' 10 | } 11 | } 12 | stage('test') { 13 | steps { 14 | sh 'npm test' 15 | } 16 | } 17 | stage('publish test results') { 18 | steps { 19 | junit 'junitresults.xml' 20 | step( 21 | [$class: 'CoberturaPublisher', 22 | autoUpdateHealth: false, 23 | autoUpdateStability: false, 24 | coberturaReportFile: 'coverage/cobertura-coverage.xml', 25 | failUnhealthy: false, 26 | failUnstable: false, 27 | maxNumberOfBuilds: 0, 28 | onlyStable: false, 29 | sourceEncoding: 'ASCII', 30 | zoomCoverageChart: false] 31 | ) 32 | } 33 | } 34 | stage('publish') { 35 | when { 36 | branch "master" 37 | } 38 | steps { 39 | withNPM(npmrcConfig:'npmrc-global') { 40 | sh 'npm publish' 41 | } 42 | } 43 | } 44 | } 45 | post { 46 | always { 47 | cleanWs() 48 | } 49 | failure { 50 | slackSend color: 'danger', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})" 51 | } 52 | success { 53 | slackSend color: 'good', message: "SUCCESS: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})" 54 | } 55 | unstable { 56 | slackSend color: 'warning', message: "UNSTABLE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})" 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # textversionjs 2 | 3 | Generate the text version of your HTML email in a second. 4 | 5 | This tool is an open source project. Feel free to use it any time in your projects! 6 | 7 | ## htmlToPlainText 8 | 9 | The function that generates plain text from email htmls. 10 | 11 | ### Params 12 | 13 | Param | Type | Required | Default value | Description 14 | --- |--- |--- |--- |--- 15 | htmlText | string | Yes | | The html version of the email 16 | styleConfig | json/javascript object | No | | Options for converting 17 | 18 | ### styleConfig 19 | 20 | Param | Type | Required | Default value | Description 21 | --- |--- |--- |--- |--- 22 | linkProcess | function | No | | Callback function to customize links appearance 23 | imgProcess | function | No | | Callback function to customize image appearance 24 | headingStyle | string | No | "underline" | Define heading appearance, options: "underline", "linebreak", "hashify" 25 | listStyle | string | No | "indention" | Define list appearance, options: "indention", "linebreak" 26 | uIndentionChar | string | No | "-" | If listStyle is indention, uIndentionChar is the character that fills the indention for unordered lists 27 | oIndentionChar | string | No | "-" | If listStyle is indention, oIndentionChar is the character that fills the indention for ordered lists after the heading number 28 | listIndentionTabs | int | No | 3 | If listStyle is indention, listIndentionTabs is the width of the indention 29 | keepNbsps | boolean | No | false | Define the behaviour of the non-braking spaces. If set to true, nbsps are not collapsed to single space. 30 | 31 | ### linkProcess 32 | 33 | Param | Type | Required | Default value | Description 34 | --- |--- |--- |--- |--- 35 | href | string | Yes | | The destination (href property) of the link 36 | linkText | string | Yes | | The text of the link 37 | 38 | ### imgProcess 39 | 40 | Param | Type | Required | Default value | Description 41 | --- |--- |--- |--- |--- 42 | src | string | Yes | | The source (src property) of the image 43 | alt | string | Yes | | The alternative text (alt property) of the image 44 | 45 | ### Examples 46 | 47 | #### Simple conversion with default style 48 | 49 | ```js 50 | var textVersion = require("textversionjs"); 51 | var htmlText = "" + 52 | "" + 53 | "Lorem ipsum dolor sic amet
" + 54 | "Lorem ipsum \"foo\" sic
amet
" + 55 | "

Lorem ipsum dolor
sic amet

" + 56 | "" + 59 | "" + 60 | ""; 61 | 62 | var plainText = textVersion(htmlText); 63 | // returns 64 | // "Lorem ipsum [dolor] (http://foo.foo) sic amet 65 | // Lorem ipsum ![foo] (http://foo.jpg) sic amet 66 | // Lorem ipsum dolor 67 | // sic amet" 68 | 69 | ``` 70 | 71 | #### Customize link appearance 72 | 73 | ```js 74 | var textVersion = require("textversionjs"); 75 | var htmlText = "

Lorem ipsum dolor sic amet

"; 76 | 77 | var styleConfig = { 78 | linkProcess: function(href, linkText){ 79 | return linkText + " " + "(" + href + ")"; 80 | } 81 | }; 82 | 83 | var plainText = textVersion(htmlText, styleConfig); 84 | // returns "Lorem ipsum (http://foo.foo) dolor sic amet" 85 | 86 | ``` 87 | 88 | #### Customize headings 89 | 90 | ```js 91 | var textVersion = require("textversionjs"); 92 | 93 | var htmlText = "

Lorem ipsum

" + 94 | "

Lorem ipsum dolor sic amet

"; 95 | 96 | var styleConfig = { 97 | headingStyle: "hashify" 98 | }; 99 | 100 | var plainText = textVersion(htmlText, styleConfig); 101 | // returns 102 | // "# Lorem ipsum 103 | // 104 | // Lorem ipsum dolor sic amet" 105 | 106 | ``` 107 | 108 | #### Customize lists 109 | 110 | ```js 111 | var textVersion = require("textversionjs"); 112 | var htmlText = "" + 116 | "
    " + 117 | "
  1. Lorem
  2. " + 118 | "
  3. ipsum
  4. " + 119 | "
"; 120 | 121 | var styleConfig = { 122 | headingStyle: "indention", 123 | uIndentionChar: "."; 124 | listIndentionTabs: 2; 125 | }; 126 | 127 | var plainText = textVersion(htmlText, styleConfig); 128 | // returns " 129 | // ..Lorem 130 | // ..ipsum 131 | // 3.Lorem 132 | // 4.ipsum" 133 | ``` 134 | 135 | Try it online in our [Demo page](http://emailtextversion.com/)! 136 | 137 | Don't forget to check out our other open source projects at [EDMdesigner](https://edmdesigner.com/). 138 | 139 | Follow us on [github](https://github.com/EDMdesigner) and [twitter](https://twitter.com/EDMdesigner)! 140 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | textversionjs.com 2 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely need to edit after that. 5 | # For technical reasons, this file is *NOT* reloaded automatically when you use 6 | # 'jekyll serve'. If you change this file, please restart the server process. 7 | 8 | # Site settings 9 | title: TextVersionJS 10 | email: info@edmdesigner.com 11 | description: > # this means to ignore newlines until "baseurl:" 12 | Generate the text version of your HTML email in a second. 13 | baseurl: "" # the subpath of your site, e.g. /blog 14 | url: "http://textversionjs.com" # the base hostname & protocol for your site 15 | gaCode: UA-42954042-8 16 | facebook_username: edmdesigner 17 | twitter_username: EDMdesigner 18 | github_username: EDMdesigner 19 | linkedin_username: company/edmdesigner-com 20 | google_username: +Edmdesigner 21 | projectname: textversionjs 22 | 23 | # Build settings 24 | markdown: kramdown 25 | -------------------------------------------------------------------------------- /docs/_includes/footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/_includes/header.html: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | -------------------------------------------------------------------------------- /docs/_includes/icon-github.html: -------------------------------------------------------------------------------- 1 | {% include icon-github.svg %}{{ include.username }} 2 | -------------------------------------------------------------------------------- /docs/_includes/icon-github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/_includes/icon-twitter.html: -------------------------------------------------------------------------------- 1 | {% include icon-twitter.svg %}{{ include.username }} 2 | -------------------------------------------------------------------------------- /docs/_includes/icon-twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include header.html %} 9 | 10 |
11 |
12 | {{ content }} 13 |
14 |
15 | 16 | {% include footer.html %} 17 | 18 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | 6 |
7 |

{{ page.title }}

8 |
9 | 10 |
11 | {{ content }} 12 |
13 | 14 |
15 | -------------------------------------------------------------------------------- /docs/_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | 6 |
7 |

{{ page.title }}

8 | 9 |
10 | 11 |
12 | {{ content }} 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /docs/_sass/_base.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Reset some basic elements 3 | */ 4 | body, h1, h2, h3, h4, h5, h6, 5 | p, blockquote, pre, hr, 6 | dl, dd, ol, ul, figure { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | 12 | 13 | /** 14 | * Basic styling 15 | */ 16 | body { 17 | font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family; 18 | color: $text-color; 19 | background-color: $background-color; 20 | -webkit-text-size-adjust: 100%; 21 | -webkit-font-feature-settings: "kern" 1; 22 | -moz-font-feature-settings: "kern" 1; 23 | -o-font-feature-settings: "kern" 1; 24 | font-feature-settings: "kern" 1; 25 | font-kerning: normal; 26 | } 27 | 28 | 29 | 30 | /** 31 | * Set `margin-bottom` to maintain vertical rhythm 32 | */ 33 | h1, h2, h3, h4, h5, h6, 34 | p, blockquote, pre, 35 | ul, ol, dl, figure, 36 | %vertical-rhythm { 37 | margin-bottom: $spacing-unit / 2; 38 | } 39 | 40 | 41 | 42 | /** 43 | * Images 44 | */ 45 | img { 46 | max-width: 100%; 47 | vertical-align: middle; 48 | } 49 | 50 | 51 | 52 | /** 53 | * Figures 54 | */ 55 | figure > img { 56 | display: block; 57 | } 58 | 59 | figcaption { 60 | font-size: $small-font-size; 61 | } 62 | 63 | 64 | 65 | /** 66 | * Lists 67 | */ 68 | ul, ol { 69 | margin-left: $spacing-unit; 70 | } 71 | 72 | li { 73 | > ul, 74 | > ol { 75 | margin-bottom: 0; 76 | } 77 | } 78 | 79 | 80 | 81 | /** 82 | * Headings 83 | */ 84 | h1, h2, h3, h4, h5, h6 { 85 | font-weight: $base-font-weight; 86 | } 87 | 88 | 89 | 90 | /** 91 | * Links 92 | */ 93 | a { 94 | color: $brand-color; 95 | text-decoration: none; 96 | 97 | &:visited { 98 | color: darken($brand-color, 15%); 99 | } 100 | 101 | &:hover { 102 | color: $text-color; 103 | text-decoration: underline; 104 | } 105 | } 106 | 107 | 108 | 109 | /** 110 | * Blockquotes 111 | */ 112 | blockquote { 113 | color: $grey-color; 114 | border-left: 4px solid $grey-color-light; 115 | padding-left: $spacing-unit / 2; 116 | font-size: 18px; 117 | letter-spacing: -1px; 118 | font-style: italic; 119 | 120 | > :last-child { 121 | margin-bottom: 0; 122 | } 123 | } 124 | 125 | 126 | 127 | /** 128 | * Code formatting 129 | */ 130 | pre, 131 | code { 132 | font-size: 15px; 133 | border: 1px solid $grey-color-light; 134 | border-radius: 3px; 135 | background-color: #eef; 136 | } 137 | 138 | code { 139 | padding: 1px 5px; 140 | } 141 | 142 | pre { 143 | padding: 8px 12px; 144 | overflow-x: auto; 145 | 146 | > code { 147 | border: 0; 148 | padding-right: 0; 149 | padding-left: 0; 150 | } 151 | } 152 | 153 | 154 | 155 | /** 156 | * Wrapper 157 | */ 158 | .wrapper { 159 | max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2)); 160 | max-width: calc(#{$content-width} - (#{$spacing-unit} * 2)); 161 | margin-right: auto; 162 | margin-left: auto; 163 | padding-right: $spacing-unit; 164 | padding-left: $spacing-unit; 165 | @extend %clearfix; 166 | 167 | @include media-query($on-laptop) { 168 | max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit})); 169 | max-width: calc(#{$content-width} - (#{$spacing-unit})); 170 | padding-right: $spacing-unit / 2; 171 | padding-left: $spacing-unit / 2; 172 | } 173 | } 174 | 175 | 176 | 177 | /** 178 | * Clearfix 179 | */ 180 | %clearfix { 181 | 182 | &:after { 183 | content: ""; 184 | display: table; 185 | clear: both; 186 | } 187 | } 188 | 189 | 190 | 191 | /** 192 | * Icons 193 | */ 194 | .icon { 195 | 196 | > svg { 197 | display: inline-block; 198 | width: 16px; 199 | height: 16px; 200 | vertical-align: middle; 201 | 202 | path { 203 | fill: $grey-color; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /docs/_sass/_layout.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Site header 3 | */ 4 | .site-header { 5 | border-top: 5px solid $grey-color-dark; 6 | border-bottom: 1px solid $grey-color-light; 7 | min-height: 56px; 8 | 9 | // Positioning context for the mobile navigation icon 10 | position: relative; 11 | } 12 | 13 | .site-title { 14 | font-size: 26px; 15 | font-weight: 300; 16 | line-height: 56px; 17 | letter-spacing: -1px; 18 | margin-bottom: 0; 19 | float: left; 20 | 21 | &, 22 | &:visited { 23 | color: $grey-color-dark; 24 | } 25 | } 26 | 27 | .site-nav { 28 | float: right; 29 | line-height: 56px; 30 | 31 | .menu-icon { 32 | display: none; 33 | } 34 | 35 | .page-link { 36 | color: $text-color; 37 | line-height: $base-line-height; 38 | 39 | // Gaps between nav items, but not on the last one 40 | &:not(:last-child) { 41 | margin-right: 20px; 42 | } 43 | } 44 | 45 | @include media-query($on-palm) { 46 | position: absolute; 47 | top: 9px; 48 | right: $spacing-unit / 2; 49 | background-color: $background-color; 50 | border: 1px solid $grey-color-light; 51 | border-radius: 5px; 52 | text-align: right; 53 | 54 | .menu-icon { 55 | display: block; 56 | float: right; 57 | width: 36px; 58 | height: 26px; 59 | line-height: 0; 60 | padding-top: 10px; 61 | text-align: center; 62 | 63 | > svg { 64 | width: 18px; 65 | height: 15px; 66 | 67 | path { 68 | fill: $grey-color-dark; 69 | } 70 | } 71 | } 72 | 73 | .trigger { 74 | clear: both; 75 | display: none; 76 | } 77 | 78 | &:hover .trigger { 79 | display: block; 80 | padding-bottom: 5px; 81 | } 82 | 83 | .page-link { 84 | display: block; 85 | padding: 5px 10px; 86 | 87 | &:not(:last-child) { 88 | margin-right: 0; 89 | } 90 | margin-left: 20px; 91 | } 92 | } 93 | } 94 | 95 | 96 | 97 | /** 98 | * Site footer 99 | */ 100 | .site-footer { 101 | border-top: 1px solid $grey-color-light; 102 | padding: $spacing-unit 0; 103 | } 104 | 105 | .footer-heading { 106 | font-size: 18px; 107 | margin-bottom: $spacing-unit / 2; 108 | } 109 | 110 | .contact-list, 111 | .social-media-list { 112 | list-style: none; 113 | margin-left: 0; 114 | } 115 | 116 | .footer-col-wrapper { 117 | font-size: 15px; 118 | color: $grey-color; 119 | margin-left: -$spacing-unit / 2; 120 | @extend %clearfix; 121 | } 122 | 123 | .footer-col { 124 | float: left; 125 | margin-bottom: $spacing-unit / 2; 126 | padding-left: $spacing-unit / 2; 127 | } 128 | 129 | .footer-col-1 { 130 | width: -webkit-calc(35% - (#{$spacing-unit} / 2)); 131 | width: calc(35% - (#{$spacing-unit} / 2)); 132 | } 133 | 134 | .footer-col-2 { 135 | width: -webkit-calc(20% - (#{$spacing-unit} / 2)); 136 | width: calc(20% - (#{$spacing-unit} / 2)); 137 | } 138 | 139 | .footer-col-3 { 140 | width: -webkit-calc(45% - (#{$spacing-unit} / 2)); 141 | width: calc(45% - (#{$spacing-unit} / 2)); 142 | } 143 | 144 | @include media-query($on-laptop) { 145 | .footer-col-1, 146 | .footer-col-2 { 147 | width: -webkit-calc(50% - (#{$spacing-unit} / 2)); 148 | width: calc(50% - (#{$spacing-unit} / 2)); 149 | } 150 | 151 | .footer-col-3 { 152 | width: -webkit-calc(100% - (#{$spacing-unit} / 2)); 153 | width: calc(100% - (#{$spacing-unit} / 2)); 154 | } 155 | } 156 | 157 | @include media-query($on-palm) { 158 | .footer-col { 159 | float: none; 160 | width: -webkit-calc(100% - (#{$spacing-unit} / 2)); 161 | width: calc(100% - (#{$spacing-unit} / 2)); 162 | } 163 | } 164 | 165 | 166 | 167 | /** 168 | * Page content 169 | */ 170 | .page-content { 171 | padding: $spacing-unit 0; 172 | } 173 | 174 | .page-heading { 175 | font-size: 20px; 176 | } 177 | 178 | .post-list { 179 | margin-left: 0; 180 | list-style: none; 181 | 182 | > li { 183 | margin-bottom: $spacing-unit; 184 | } 185 | } 186 | 187 | .post-meta { 188 | font-size: $small-font-size; 189 | color: $grey-color; 190 | } 191 | 192 | .post-link { 193 | display: block; 194 | font-size: 24px; 195 | } 196 | 197 | 198 | 199 | /** 200 | * Posts 201 | */ 202 | .post-header { 203 | margin-bottom: $spacing-unit; 204 | } 205 | 206 | .post-title { 207 | font-size: 42px; 208 | letter-spacing: -1px; 209 | line-height: 1; 210 | 211 | @include media-query($on-laptop) { 212 | font-size: 36px; 213 | } 214 | } 215 | 216 | .post-content { 217 | margin-bottom: $spacing-unit; 218 | 219 | h2 { 220 | font-size: 32px; 221 | 222 | @include media-query($on-laptop) { 223 | font-size: 28px; 224 | } 225 | } 226 | 227 | h3 { 228 | font-size: 26px; 229 | 230 | @include media-query($on-laptop) { 231 | font-size: 22px; 232 | } 233 | } 234 | 235 | h4 { 236 | font-size: 20px; 237 | 238 | @include media-query($on-laptop) { 239 | font-size: 18px; 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /docs/_sass/_syntax-highlighting.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Syntax highlighting styles 3 | */ 4 | .highlight { 5 | background: #fff; 6 | @extend %vertical-rhythm; 7 | 8 | .highlighter-rouge & { 9 | background: #eef; 10 | } 11 | 12 | .c { color: #998; font-style: italic } // Comment 13 | .err { color: #a61717; background-color: #e3d2d2 } // Error 14 | .k { font-weight: bold } // Keyword 15 | .o { font-weight: bold } // Operator 16 | .cm { color: #998; font-style: italic } // Comment.Multiline 17 | .cp { color: #999; font-weight: bold } // Comment.Preproc 18 | .c1 { color: #998; font-style: italic } // Comment.Single 19 | .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special 20 | .gd { color: #000; background-color: #fdd } // Generic.Deleted 21 | .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific 22 | .ge { font-style: italic } // Generic.Emph 23 | .gr { color: #a00 } // Generic.Error 24 | .gh { color: #999 } // Generic.Heading 25 | .gi { color: #000; background-color: #dfd } // Generic.Inserted 26 | .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific 27 | .go { color: #888 } // Generic.Output 28 | .gp { color: #555 } // Generic.Prompt 29 | .gs { font-weight: bold } // Generic.Strong 30 | .gu { color: #aaa } // Generic.Subheading 31 | .gt { color: #a00 } // Generic.Traceback 32 | .kc { font-weight: bold } // Keyword.Constant 33 | .kd { font-weight: bold } // Keyword.Declaration 34 | .kp { font-weight: bold } // Keyword.Pseudo 35 | .kr { font-weight: bold } // Keyword.Reserved 36 | .kt { color: #458; font-weight: bold } // Keyword.Type 37 | .m { color: #099 } // Literal.Number 38 | .s { color: #d14 } // Literal.String 39 | .na { color: #008080 } // Name.Attribute 40 | .nb { color: #0086B3 } // Name.Builtin 41 | .nc { color: #458; font-weight: bold } // Name.Class 42 | .no { color: #008080 } // Name.Constant 43 | .ni { color: #800080 } // Name.Entity 44 | .ne { color: #900; font-weight: bold } // Name.Exception 45 | .nf { color: #900; font-weight: bold } // Name.Function 46 | .nn { color: #555 } // Name.Namespace 47 | .nt { color: #000080 } // Name.Tag 48 | .nv { color: #008080 } // Name.Variable 49 | .ow { font-weight: bold } // Operator.Word 50 | .w { color: #bbb } // Text.Whitespace 51 | .mf { color: #099 } // Literal.Number.Float 52 | .mh { color: #099 } // Literal.Number.Hex 53 | .mi { color: #099 } // Literal.Number.Integer 54 | .mo { color: #099 } // Literal.Number.Oct 55 | .sb { color: #d14 } // Literal.String.Backtick 56 | .sc { color: #d14 } // Literal.String.Char 57 | .sd { color: #d14 } // Literal.String.Doc 58 | .s2 { color: #d14 } // Literal.String.Double 59 | .se { color: #d14 } // Literal.String.Escape 60 | .sh { color: #d14 } // Literal.String.Heredoc 61 | .si { color: #d14 } // Literal.String.Interpol 62 | .sx { color: #d14 } // Literal.String.Other 63 | .sr { color: #009926 } // Literal.String.Regex 64 | .s1 { color: #d14 } // Literal.String.Single 65 | .ss { color: #990073 } // Literal.String.Symbol 66 | .bp { color: #999 } // Name.Builtin.Pseudo 67 | .vc { color: #008080 } // Name.Variable.Class 68 | .vg { color: #008080 } // Name.Variable.Global 69 | .vi { color: #008080 } // Name.Variable.Instance 70 | .il { color: #099 } // Literal.Number.Integer.Long 71 | } 72 | -------------------------------------------------------------------------------- /docs/css/colors.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #F4F4F4; 3 | } 4 | 5 | a { 6 | color: #58585A; 7 | } 8 | 9 | .bg-white { 10 | background-color: #FFFFFF; 11 | color: #58585A; 12 | } 13 | 14 | .bg-brown { 15 | background-color: #F8F7F0; 16 | color: #58585A; 17 | } 18 | 19 | .bg-gray { 20 | background-color: #58585A; 21 | color: #FFFFFF; 22 | } 23 | 24 | .bg-red { 25 | background-color: #C92C1E; 26 | color: #FFFFFF; 27 | } 28 | 29 | .bg-red-hover:hover { 30 | background-color: #C92C1E; 31 | color: #FFFFFF; 32 | } 33 | 34 | .bg-blue { 35 | background-color: #2879CB; 36 | color: #FFFFFF; 37 | } 38 | 39 | .bg-blue-hover:hover { 40 | background-color: #2879CB; 41 | color: #FFFFFF; 42 | } 43 | 44 | .bg-dark-brown { 45 | background-color: #E8E3CE; 46 | color: #58585A; 47 | } -------------------------------------------------------------------------------- /docs/css/components.css: -------------------------------------------------------------------------------- 1 | .button { 2 | display: inline-block; 3 | 4 | padding: 10px; 5 | margin-top: 15px; 6 | 7 | border-radius: 30px; 8 | 9 | font-size: 16px; 10 | line-height: 16px; 11 | 12 | text-decoration: none; 13 | 14 | transition: all .5s; 15 | } 16 | 17 | .button:hover { 18 | box-shadow: 2px 2px 5px #888888; 19 | } 20 | 21 | .header-button { 22 | display: block; 23 | 24 | font-size: 20px; 25 | line-height: 35px; 26 | 27 | text-align: center; 28 | text-decoration: none; 29 | 30 | transition: all .5s; 31 | } 32 | 33 | .new-decorator { 34 | position: absolute; 35 | width: 100px; 36 | height: 100px; 37 | margin-left: -7px; 38 | margin-top: -5px; 39 | } 40 | 41 | .socials { 42 | display: flex; 43 | } 44 | 45 | .socials a { 46 | display: inline-block; 47 | width: 28px; 48 | height: 28px; 49 | overflow: hidden; 50 | text-indent: -999px; 51 | background: url(img/socials.png) 0 0 no-repeat; 52 | } 53 | 54 | .socials a#f-fb { 55 | background-position: 0 -50px; 56 | } 57 | .socials a#f-tw { 58 | background-position: 0 -73px; 59 | } 60 | .socials a#f-ln { 61 | background-position: 0 -97px; 62 | } 63 | .socials a#f-gp { 64 | background-position: 0 -123px; 65 | } 66 | .socials a#f-rss { 67 | background-position: 0 -150px; 68 | } 69 | 70 | @media all and (max-width: 599px) { 71 | .button { 72 | display: block; 73 | margin: 30px 50px; 74 | padding: 20px; 75 | 76 | text-align: center; 77 | } 78 | } -------------------------------------------------------------------------------- /docs/css/defaults.css: -------------------------------------------------------------------------------- 1 | html, body, div, p, ul, ol, li, h1, h2, h3, h4, h5, h6 { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | font-size:10px; 8 | line-height:10px; 9 | } 10 | 11 | h1, h2, h3, h4, h5, h6 { 12 | font-family: "Roboto", sans-serif; 13 | } 14 | 15 | 16 | h1 { 17 | font-size: 28px; 18 | line-height: 32px; 19 | 20 | margin-bottom: 24px; 21 | } 22 | 23 | h2 { 24 | font-size: 24px; 25 | line-height: 28px; 26 | 27 | margin-bottom: 20px; 28 | } 29 | 30 | h3 { 31 | font-size: 20px; 32 | line-height: 24px; 33 | 34 | margin-bottom: 16px; 35 | } 36 | 37 | p { 38 | font-size: 16px; 39 | line-height: 20px; 40 | 41 | font-family: Georgia, Arial, sans-serif; 42 | } 43 | -------------------------------------------------------------------------------- /docs/css/elements.css: -------------------------------------------------------------------------------- 1 | #header { 2 | position: relative; 3 | z-index: 2; 4 | } 5 | 6 | #header #menu { 7 | position: absolute; 8 | 9 | width: 100%; 10 | 11 | top: 0; 12 | left: 0; 13 | 14 | transition: 1s all; 15 | } 16 | 17 | #header #hero { 18 | padding-top: 130px; 19 | 20 | text-align: center; 21 | } 22 | 23 | #content { 24 | position: relative; 25 | z-index: 1; 26 | } 27 | 28 | #footer { 29 | position: relative; 30 | z-index: 1; 31 | 32 | text-align: center; 33 | } -------------------------------------------------------------------------------- /docs/css/images.css: -------------------------------------------------------------------------------- 1 | img { 2 | width: 100%; 3 | height: auto; 4 | 5 | display: block; 6 | 7 | margin: 0; 8 | } 9 | 10 | .w200 { 11 | max-width: 200px; 12 | } 13 | 14 | @media all and (max-width: 599px) { 15 | .w200 { 16 | margin-bottom: 20px; 17 | margin-left: auto; 18 | margin-right: auto; 19 | } 20 | } -------------------------------------------------------------------------------- /docs/css/img/socials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDMdesigner/textversionjs/1f53cbb97db4b1ca2d297e600f4253578b884740/docs/css/img/socials.png -------------------------------------------------------------------------------- /docs/css/layout.css: -------------------------------------------------------------------------------- 1 | .container600 { 2 | width: 600px; 3 | margin: 0 auto; 4 | } 5 | 6 | .container800 { 7 | width: 800px; 8 | margin: 0 auto; 9 | } 10 | 11 | .cols > .w33p { 12 | width: 33.33%; 13 | float: left; 14 | } 15 | 16 | .cols > .w66p { 17 | width: 66.66%; 18 | float: left; 19 | } 20 | 21 | .cols > .w50p { 22 | width: 50%; 23 | float: left; 24 | } 25 | 26 | .cols > .w25p { 27 | width: 25%; 28 | float: left; 29 | } 30 | 31 | .cols > .w20p { 32 | width: 20%; 33 | flex-grow: 1; 34 | } 35 | 36 | .cols:after { 37 | content: ""; 38 | display: block; 39 | clear: both; 40 | } 41 | 42 | .cols-r > .w33p { 43 | width: 33.33%; 44 | float: right; 45 | } 46 | 47 | .cols-r > .w66p { 48 | width: 66.66%; 49 | float: right; 50 | } 51 | 52 | .cols-r > .w50p { 53 | width: 50%; 54 | float: right; 55 | } 56 | 57 | .cols-r > .w25p { 58 | width: 25%; 59 | float: right; 60 | } 61 | 62 | .cols-r > .w20p { 63 | width: 20%; 64 | float: right; 65 | } 66 | 67 | .cols-r:after { 68 | content: ""; 69 | display: block; 70 | clear: both; 71 | } 72 | 73 | .clr { 74 | clear: both; 75 | } 76 | 77 | @media all and (max-width: 799px) { 78 | .container800 { 79 | width: 100%; 80 | } 81 | } 82 | 83 | @media all and (max-width: 599px) { 84 | .container600 { 85 | width: 100%; 86 | } 87 | 88 | .reorder > .w33p { 89 | width: 100%; 90 | } 91 | 92 | .reorder > .w66p { 93 | width: 100%; 94 | } 95 | 96 | .reorder > .w50p { 97 | width: 100%; 98 | } 99 | 100 | .reorder > .w25p { 101 | width: 100%; 102 | } 103 | 104 | .reorder > .w20p { 105 | width: 100%; 106 | } 107 | } -------------------------------------------------------------------------------- /docs/css/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Only the main Sass file needs front matter (the dashes are enough) 3 | --- 4 | @charset "utf-8"; 5 | 6 | 7 | 8 | // Our variables 9 | $base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 10 | $base-font-size: 16px; 11 | $base-font-weight: 400; 12 | $small-font-size: $base-font-size * 0.875; 13 | $base-line-height: 1.5; 14 | 15 | $spacing-unit: 30px; 16 | 17 | $text-color: #111; 18 | $background-color: #fdfdfd; 19 | $brand-color: #58585A; 20 | 21 | $grey-color: #828282; 22 | $grey-color-light: lighten($grey-color, 40%); 23 | $grey-color-dark: darken($grey-color, 25%); 24 | 25 | // Width of the content area 26 | $content-width: 800px; 27 | 28 | $on-palm: 600px; 29 | $on-laptop: 800px; 30 | 31 | 32 | 33 | // Use media queries like this: 34 | // @include media-query($on-palm) { 35 | // .wrapper { 36 | // padding-right: $spacing-unit / 2; 37 | // padding-left: $spacing-unit / 2; 38 | // } 39 | // } 40 | @mixin media-query($device) { 41 | @media screen and (max-width: $device) { 42 | @content; 43 | } 44 | } 45 | 46 | 47 | 48 | // Import partials from `sass_dir` (defaults to `_sass`) 49 | @import 50 | "base", 51 | "layout", 52 | "syntax-highlighting" 53 | ; 54 | 55 | textarea { 56 | width: 100%; 57 | height: 300px; 58 | } -------------------------------------------------------------------------------- /docs/css/spacings.css: -------------------------------------------------------------------------------- 1 | .sm-padding { 2 | padding: 10px; 3 | } 4 | 5 | .sm-padding-tb { 6 | padding-top: 10px; 7 | padding-bottom: 10px; 8 | } 9 | 10 | .m-padding-tb { 11 | padding-top: 30px; 12 | padding-bottom: 30px; 13 | } 14 | 15 | .l-padding-tb { 16 | padding-top: 100px; 17 | padding-bottom: 100px; 18 | } 19 | -------------------------------------------------------------------------------- /docs/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Documentation 4 | permalink: /documentation/ 5 | --- 6 | 7 | # textversionjs 8 | 9 | Generate the text version of your HTML email in a second. 10 | 11 | This tool is an open source project. Feel free to use it any time in your projects! 12 | 13 | ## htmlToPlainText 14 | 15 | The function that generates plain text from email htmls. 16 | 17 | ### Params 18 | 19 | Param | Type | Required | Default value | Description 20 | --- |--- |--- |--- |--- 21 | htmlText | string | Yes | | The html version of the email 22 | styleConfig | json | No | | Options for converting 23 | 24 | ### styleConfig 25 | 26 | Param | Type | Required | Default value | Description 27 | --- |--- |--- |--- |--- 28 | linkProcess | function | No | | Callback function to customize links appearance 29 | imgProcess | function | No | | Callback function to customize image appearance 30 | headingStyle | string | No | "underline" | Define heading appearance, options: "underline", "linebreak", "hashify" 31 | listStyle | string | No | "indention" | Define list appearance, options: "indention", "linebreak" 32 | uIndentionChar | string | No | "-" | If listStyle is indention, uIndentionChar is the character that fills the indention for unordered lists 33 | oIndentionChar | string | No | "-" | If listStyle is indention, oIndentionChar is the character that fills the indention for ordered lists after the heading number 34 | listIndentionTabs | int | No | 3 | If listStyle is indention, listIndentionTabs is the width of the indention 35 | 36 | ### linkProcess 37 | 38 | Param | Type | Required | Default value | Description 39 | --- |--- |--- |--- |--- 40 | href | string | Yes | | The destination (href property) of the link 41 | linkText | string | Yes | | The text of the link 42 | 43 | ### imgProcess 44 | 45 | Param | Type | Required | Default value | Description 46 | --- |--- |--- |--- |--- 47 | src | string | Yes | | The source (src property) of the image 48 | alt | string | Yes | | The alternative text (alt property) of the image 49 | 50 | ### Examples 51 | 52 | #### Simple conversion with default style 53 | 54 | ```js 55 | var textVersion = require("textversionjs"); 56 | var htmlText = "" + 57 | "" + 58 | "Lorem ipsum dolor sic amet
" + 59 | "Lorem ipsum \"foo\" sic
amet
" + 60 | "

Lorem ipsum dolor
sic amet

" + 61 | "" + 64 | "" + 65 | ""; 66 | 67 | var plainText = textVersion(htmlText); 68 | // returns 69 | // "Lorem ipsum [dolor] (http://foo.foo) sic amet 70 | // Lorem ipsum ![foo] (http://foo.jpg) sic amet 71 | // Lorem ipsum dolor 72 | // sic amet" 73 | 74 | ``` 75 | 76 | #### Customize link appearance 77 | 78 | ```js 79 | var textVersion = require("textversionjs"); 80 | var htmlText = "

Lorem ipsum dolor sic amet

"; 81 | 82 | var styleConfig: { 83 | linkStyle: function(href, linkText){ 84 | return linkText + " " + "(" + href + ")"; 85 | } 86 | }; 87 | 88 | var plainText = textVersion(htmlText, styleConfig); 89 | // returns "Lorem ipsum (http://foo.foo) dolor sic amet" 90 | 91 | ``` 92 | 93 | #### Customize headings 94 | 95 | ```js 96 | var textVersion = require("textversionjs"); 97 | 98 | var htmlText = "

Lorem ipsum

" + 99 | "

Lorem ipsum dolor sic amet

"; 100 | 101 | var styleConfig: { 102 | headingStyle: "hashify" 103 | }; 104 | 105 | var plainText = textVersion(htmlText, styleConfig); 106 | // returns 107 | // "# Lorem ipsum 108 | // 109 | // Lorem ipsum dolor sic amet" 110 | 111 | ``` 112 | 113 | #### Customize lists 114 | 115 | ```js 116 | var textVersion = require("textversionjs"); 117 | var htmlText = "" + 121 | "
    " + 122 | "
  1. Lorem
  2. " + 123 | "
  3. ipsum
  4. " + 124 | "
"; 125 | 126 | var styleConfig: { 127 | headingStyle: "indention", 128 | uIndentionChar: "."; 129 | listIndentionTabs: 2; 130 | }; 131 | 132 | var plainText = textVersion(htmlText, styleConfig); 133 | // returns " 134 | // ..Lorem 135 | // ..ipsum 136 | // 3.Lorem 137 | // 4.ipsum" 138 | ``` 139 | 140 | Try it online in our [Demo page](http://emailtextversion.com/)! 141 | 142 | Don't forget to check out our other open source projects at [EDMdesigner](http://edmdesigner.com/). 143 | 144 | Follow us on [github](https://github.com/EDMdesigner) and [twitter](https://twitter.com/EDMdesigner)! -------------------------------------------------------------------------------- /docs/feed.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | --- 4 | 5 | 6 | 7 | {{ site.title | xml_escape }} 8 | {{ site.description | xml_escape }} 9 | {{ site.url }}{{ site.baseurl }}/ 10 | 11 | {{ site.time | date_to_rfc822 }} 12 | {{ site.time | date_to_rfc822 }} 13 | Jekyll v{{ jekyll.version }} 14 | {% for post in site.posts limit:10 %} 15 | 16 | {{ post.title | xml_escape }} 17 | {{ post.content | xml_escape }} 18 | {{ post.date | date_to_rfc822 }} 19 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 20 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 21 | {% for tag in post.tags %} 22 | {{ tag | xml_escape }} 23 | {% endfor %} 24 | {% for cat in post.categories %} 25 | {{ cat | xml_escape }} 26 | {% endfor %} 27 | 28 | {% endfor %} 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDMdesigner/textversionjs/1f53cbb97db4b1ca2d297e600f4253578b884740/docs/img/logo.png -------------------------------------------------------------------------------- /docs/img/socials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EDMdesigner/textversionjs/1f53cbb97db4b1ca2d297e600f4253578b884740/docs/img/socials.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 6 |
7 |

TextVersionJS

8 | 9 |

{{ site.description }}

10 |

This tool is based on the TextVersionJS open source project. Feel free to use it any time in your projects!

11 | 12 |

HTML to plain text converter online

13 | 14 |

You just have to copy & paste your email HTML code in the first textarea and press the generate button.

15 | 16 |

Paste your email HTML here

17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 |

The text version

25 |
26 | 27 |
28 | 29 | 39 |
40 | -------------------------------------------------------------------------------- /docs/js/textversion.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.textversion = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o tags including the tag itself 54 | tmp = tmp.replace(/<\/body>.*/i, ""); 55 | tmp = tmp.replace(/.*]*>/i, ""); 56 | 57 | // remove inbody scripts and styles 58 | tmp = tmp.replace(/<(script|style)( [^>]*)*>((?!<\/\1( [^>]*)*>).)*<\/\1>/gi, ""); 59 | 60 | // remove all tags except that are being handled separately 61 | tmp = tmp.replace(/<(\/)?((?!h[1-6]( [^>]*)*>)(?!img( [^>]*)*>)(?!a( [^>]*)*>)(?!ul( [^>]*)*>)(?!ol( [^>]*)*>)(?!li( [^>]*)*>)(?!p( [^>]*)*>)(?!div( [^>]*)*>)(?!td( [^>]*)*>)(?!br( [^>]*)*>)[^>\/])[^>]*>/gi, ""); 62 | 63 | // remove or replace images - replacement texts with <> tags will be removed also, if not intentional, try to use other notation 64 | tmp = tmp.replace(/]*)>/gi, function (str, imAttrs) { 65 | var imSrc = ""; 66 | var imAlt = ""; 67 | var imSrcResult = /src="([^"]*)"/i.exec(imAttrs); 68 | var imAltResult = /alt="([^"]*)"/i.exec(imAttrs); 69 | if (imSrcResult !== null) { 70 | imSrc = imSrcResult[1]; 71 | } 72 | if (imAltResult !== null) { 73 | imAlt = imAltResult[1]; 74 | } 75 | if (typeof imgProcess === "function") { 76 | return imgProcess(imSrc, imAlt); 77 | } 78 | if (imAlt === "") { 79 | return "![image] (" + imSrc + ")"; 80 | } 81 | return "![" + imAlt + "] (" + imSrc + ")"; 82 | }); 83 | 84 | function createListReplaceCb() { 85 | return function (match, listType, listAttributes, listBody) { 86 | var liIndex = 0; 87 | if (listAttributes && /start="([0-9]+)"/i.test(listAttributes)) { 88 | liIndex = /start="([0-9]+)"/i.exec(listAttributes)[1] - 1; 89 | } 90 | var plainListItem = "

" + listBody.replace(/]*>(((?!]*>)(?!<\/li>).)*)<\/li>/gi, function (str, listItem) { 91 | var actSubIndex = 0; 92 | var plainListLine = listItem.replace(/(^|(
))(?!

)/gi, function () { 93 | if (listType === "o" && actSubIndex === 0) { 94 | liIndex += 1; 95 | actSubIndex += 1; 96 | return "
" + liIndex + populateChar(oIndentionChar, listIndentionTabs - String(liIndex).length); 97 | } 98 | return "
" + uIndention; 99 | }); 100 | return plainListLine; 101 | }) + "

"; 102 | return plainListItem; 103 | }; 104 | } 105 | 106 | // handle lists 107 | if (listStyle === "linebreak") { 108 | tmp = tmp.replace(/<\/?ul[^>]*>|<\/?ol[^>]*>|<\/?li[^>]*>/gi, "\n"); 109 | } else if (listStyle === "indention") { 110 | while (/<(o|u)l[^>]*>(.*)<\/\1l>/gi.test(tmp)) { 111 | tmp = tmp.replace(/<(o|u)l([^>]*)>(((?!<(o|u)l[^>]*>)(?!<\/(o|u)l>).)*)<\/\1l>/gi, createListReplaceCb()); 112 | } 113 | } 114 | 115 | // handle headings 116 | if (headingStyle === "linebreak") { 117 | tmp = tmp.replace(/]*>([^<]*)<\/h\1>/gi, "\n$2\n"); 118 | } else if (headingStyle === "underline") { 119 | tmp = tmp.replace(/]*>(((?!<\/h1>).)*)<\/h1>/gi, function (str, p1) { 120 | return "\n \n" + p1 + "\n" + populateChar("=", p1.length) + "\n \n"; 121 | }); 122 | tmp = tmp.replace(/]*>(((?!<\/h2>).)*)<\/h2>/gi, function (str, p1) { 123 | return "\n \n" + p1 + "\n" + populateChar("-", p1.length) + "\n \n"; 124 | }); 125 | tmp = tmp.replace(/]*>(((?!<\/h\1>).)*)<\/h\1>/gi, function (str, p1, p2) { 126 | return "\n \n" + p2 + "\n \n"; 127 | }); 128 | } else if (headingStyle === "hashify") { 129 | tmp = tmp.replace(/]*>([^<]*)<\/h\1>/gi, function (str, p1, p2) { 130 | return "\n \n" + populateChar("#", p1) + " " + p2 + "\n \n"; 131 | }); 132 | } 133 | 134 | // replace
s, s, and

s with linebreaks 135 | tmp = tmp.replace(/]*)*>|]*)*>|<\/p( [^>]*)*>|]*)*>|<\/div( [^>]*)*>|]*)*>|<\/td( [^>]*)*>/gi, "\n"); 136 | 137 | // replace b links with b (href) or as described in the linkProcess function 138 | tmp = tmp.replace(/]*href="([^"]*)"[^>]*>([^<]+)<\/a[^>]*>/gi, function (str, href, linkText) { 139 | if (typeof linkProcess === "function") { 140 | return linkProcess(href, linkText); 141 | } 142 | return " [" + linkText + "] (" + href + ") "; 143 | }); 144 | 145 | // remove whitespace from empty lines excluding nbsp 146 | tmp = tmp.replace(/\n[ \t\f]*/gi, "\n"); 147 | 148 | // remove duplicated empty lines 149 | tmp = tmp.replace(/\n\n+/gi, "\n"); 150 | 151 | // remove duplicated spaces including non braking spaces 152 | tmp = tmp.replace(/( | |\t)+/gi, " "); 153 | 154 | // remove line starter spaces 155 | tmp = tmp.replace(/\n +/gi, "\n"); 156 | 157 | // remove content starter spaces 158 | tmp = tmp.replace(/^ +/gi, ""); 159 | 160 | // remove first empty line 161 | while (tmp.indexOf("\n") === 0) { 162 | tmp = tmp.substring(1); 163 | } 164 | 165 | // put a new line at the end 166 | if (tmp.length === 0 || tmp.lastIndexOf("\n") !== tmp.length - 1) { 167 | tmp += "\n"; 168 | } 169 | 170 | return tmp; 171 | } 172 | 173 | (function (name, definition) { 174 | if (this && typeof this.define === "function") { 175 | // AMD 176 | this.define(definition); 177 | } else if (typeof module !== "undefined" && module.exports) { 178 | // Node.js 179 | module.exports = definition(); 180 | } else { 181 | // Browser 182 | var theModule = definition(); 183 | var global = this; 184 | var old = global[name]; 185 | theModule.noConflict = function () { 186 | global[name] = old; 187 | return theModule; 188 | }; 189 | global[name] = theModule; 190 | } 191 | })("createTextVersion", function () { 192 | return htmlToPlainText; 193 | }); 194 | 195 | },{}]},{},["/home/smiska/workspace/EDMdesigner/textversionjs/src/textversion.js"])("/home/smiska/workspace/EDMdesigner/textversionjs/src/textversion.js") 196 | }); 197 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvdGV4dHZlcnNpb24uanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztBQ0FBLElBQUksZUFBZSxTQUFmLFlBQWUsQ0FBUyxFQUFULEVBQWEsTUFBYixFQUFvQjtBQUN0QyxLQUFJLFNBQVMsRUFBYjtBQUNBLE1BQUksSUFBSSxJQUFFLENBQVYsRUFBYSxJQUFFLE1BQWYsRUFBdUIsS0FBSyxDQUE1QixFQUE4QjtBQUM3QixZQUFVLEVBQVY7QUFDQTtBQUNELFFBQU8sTUFBUDtBQUNBLENBTkQ7O0FBUUEsU0FBUyxlQUFULENBQXlCLFFBQXpCLEVBQW1DLFdBQW5DLEVBQWdEOztBQUUvQztBQUNBLEtBQUksY0FBYyxJQUFsQjtBQUNBLEtBQUksYUFBYSxJQUFqQjtBQUNBLEtBQUksZUFBZSxXQUFuQixDQUwrQyxDQUtmO0FBQ2hDLEtBQUksWUFBWSxXQUFoQixDQU4rQyxDQU1sQjtBQUM3QixLQUFJLGlCQUFpQixHQUFyQjtBQUNBLEtBQUksb0JBQW9CLENBQXhCO0FBQ0EsS0FBSSxpQkFBaUIsR0FBckI7O0FBRUE7QUFDQSxLQUFHLENBQUMsQ0FBQyxXQUFMLEVBQWlCO0FBQ2hCLE1BQUcsT0FBTyxZQUFZLFdBQW5CLEtBQW1DLFVBQXRDLEVBQWtEO0FBQ2pELGlCQUFjLFlBQVksV0FBMUI7QUFDQTtBQUNELE1BQUcsT0FBTyxZQUFZLFVBQW5CLEtBQWtDLFVBQXJDLEVBQWlEO0FBQ2hELGdCQUFhLFlBQVksVUFBekI7QUFDQTtBQUNELE1BQUcsQ0FBQyxDQUFDLFlBQVksWUFBakIsRUFBK0I7QUFDOUIsa0JBQWUsWUFBWSxZQUEzQjtBQUNBO0FBQ0QsTUFBRyxDQUFDLENBQUMsWUFBWSxTQUFqQixFQUE0QjtBQUMzQixlQUFZLFlBQVksU0FBeEI7QUFDQTtBQUNELE1BQUcsQ0FBQyxDQUFDLFlBQVksY0FBakIsRUFBaUM7QUFDaEMsb0JBQWlCLFlBQVksY0FBN0I7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QUVPLElBQUksT0FBTyxNQUFQLEtBQWtCLFdBQWxCLElBQWlDLE9BQU8sT0FBNUMsRUFBcUQ7QUFBRTtBQUM3RCxTQUFPLE9BQVAsR0FBaUIsWUFBakI7QUFDQSxFQUZNLE1BRUE7QUFBRTtBQUNSLE1BQUksWUFBWSxZQUFoQjtBQUNBLE1BQUksU0FBUyxJQUFiO0FBQ0EsTUFBSSxNQUFNLE9BQU8sSUFBUCxDQUFWO0FBQ0EsWUFBVSxVQUFWLEdBQXVCLFlBQVk7QUFDbEMsVUFBTyxJQUFQLElBQWUsR0FBZjtBQUNBLFVBQU8sU0FBUDtBQUNBLEdBSEQ7QUFJQSxTQUFPLElBQVAsSUFBZSxTQUFmO0FBQ0E7QUFDRCxDQWZELEVBZUcsbUJBZkgsRUFld0IsWUFBWTtBQUNuQyxRQUFPLGVBQVA7QUFDQSxDQWpCRCIsImZpbGUiOiJnZW5lcmF0ZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt2YXIgZj1uZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpO3Rocm93IGYuY29kZT1cIk1PRFVMRV9OT1RfRk9VTkRcIixmfXZhciBsPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChsLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGwsbC5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCJ2YXIgcG9wdWxhdGVDaGFyID0gZnVuY3Rpb24oY2gsIGFtb3VudCl7XG5cdHZhciByZXN1bHQgPSBcIlwiO1xuXHRmb3IodmFyIGk9MDsgaTxhbW91bnQ7IGkgKz0gMSl7XG5cdFx0cmVzdWx0ICs9IGNoO1xuXHR9XG5cdHJldHVybiByZXN1bHQ7XG59O1xuXG5mdW5jdGlvbiBodG1sVG9QbGFpblRleHQoaHRtbFRleHQsIHN0eWxlQ29uZmlnKSB7XG5cblx0Ly8gZGVmaW5lIGRlZmF1bHQgc3R5bGVDb25maWdcblx0dmFyIGxpbmtQcm9jZXNzID0gbnVsbDtcblx0dmFyIGltZ1Byb2Nlc3MgPSBudWxsO1xuXHR2YXIgaGVhZGluZ1N0eWxlID0gXCJ1bmRlcmxpbmVcIjsgLy8gaGFzaGlmeSwgYnJlYWtsaW5lXG5cdHZhciBsaXN0U3R5bGUgPSBcImluZGVudGlvblwiOyAvLyBpbmRlbnRpb24sIGxpbmVicmVha1xuXHR2YXIgdUluZGVudGlvbkNoYXIgPSBcIi1cIjtcblx0dmFyIGxpc3RJbmRlbnRpb25UYWJzID0gMztcblx0dmFyIG9JbmRlbnRpb25DaGFyID0gXCItXCI7XG5cblx0Ly8gb3IgYWNjZXB0IHVzZXIgZGVmaW5lZCBjb25maWdcblx0aWYoISFzdHlsZUNvbmZpZyl7XG5cdFx0aWYodHlwZW9mIHN0eWxlQ29uZmlnLmxpbmtQcm9jZXNzID09PSBcImZ1bmN0aW9uXCIpIHtcblx0XHRcdGxpbmtQcm9jZXNzID0gc3R5bGVDb25maWcubGlua1Byb2Nlc3M7XG5cdFx0fVxuXHRcdGlmKHR5cGVvZiBzdHlsZUNvbmZpZy5pbWdQcm9jZXNzID09PSBcImZ1bmN0aW9uXCIpIHtcblx0XHRcdGltZ1Byb2Nlc3MgPSBzdHlsZUNvbmZpZy5pbWdQcm9jZXNzO1xuXHRcdH1cblx0XHRpZighIXN0eWxlQ29uZmlnLmhlYWRpbmdTdHlsZSkge1xuXHRcdFx0aGVhZGluZ1N0eWxlID0gc3R5bGVDb25maWcuaGVhZGluZ1N0eWxlO1xuXHRcdH1cblx0XHRpZighIXN0eWxlQ29uZmlnLmxpc3RTdHlsZSkge1xuXHRcdFx0bGlzdFN0eWxlID0gc3R5bGVDb25maWcubGlzdFN0eWxlO1xuXHRcdH1cblx0XHRpZighIXN0eWxlQ29uZmlnLnVJbmRlbnRpb25DaGFyKSB7XG5cdFx0XHR1SW5kZW50aW9uQ2hhciA9IHN0eWxlQ29uZmlnLnVJbmRlbnRpb25DaGFyO1xuXHRcdH1cblx0XHRpZighIXN0eWxlQ29uZmlnLmxpc3RJbmRlbnRpb25UYWJzKSB7XG5cdFx0XHRsaXN0SW5kZW50aW9uVGFicyA9IHN0eWxlQ29uZmlnLmxpc3RJbmRlbnRpb25UYWJzO1xuXHRcdH1cblx0XHRpZighIXN0eWxlQ29uZmlnLm9JbmRlbnRpb25DaGFyKSB7XG5cdFx0XHRvSW5kZW50aW9uQ2hhciA9IHN0eWxlQ29uZmlnLm9JbmRlbnRpb25DaGFyO1xuXHRcdH1cblx0fVxuXG5cdHZhciB1SW5kZW50aW9uID0gcG9wdWxhdGVDaGFyKHVJbmRlbnRpb25DaGFyLCBsaXN0SW5kZW50aW9uVGFicyk7XG5cblx0Ly8gcmVtb3ZlbCBhbGwgXFxuIGxpbmVicmVha3Ncblx0dmFyIHRtcCA9IFN0cmluZyhodG1sVGV4dCkucmVwbGFjZSgvXFxufFxcci9nLCBcIiBcIik7XG5cblx0Ly8gcmVtb3ZlIGV2ZXJ5dGhpbmcgYmVmb3JlIGFuZCBhZnRlciA8Ym9keT4gdGFncyBpbmNsdWRpbmcgdGhlIHRhZyBpdHNlbGZcblx0dG1wID0gdG1wLnJlcGxhY2UoLzxcXC9ib2R5Pi4qL2ksIFwiXCIpO1xuXHR0bXAgPSB0bXAucmVwbGFjZSgvLio8Ym9keVtePl0qPi9pLCBcIlwiKTtcblxuXHQvLyByZW1vdmUgaW5ib2R5IHNjcmlwdHMgYW5kIHN0eWxlc1xuXHR0bXAgPSB0bXAucmVwbGFjZSgvPChzY3JpcHR8c3R5bGUpKCBbXj5dKikqPigoPyE8XFwvXFwxKCBbXj5dKikqPikuKSo8XFwvXFwxPi9naSwgXCJcIik7XG5cblx0Ly8gcmVtb3ZlIGFsbCB0YWdzIGV4Y2VwdCB0aGF0IGFyZSBiZWluZyBoYW5kbGVkIHNlcGFyYXRlbHlcblx0dG1wID0gdG1wLnJlcGxhY2UoLzwoXFwvKT8oKD8haFsxLTZdKCBbXj5dKikqPikoPyFpbWcoIFtePl0qKSo+KSg/IWEoIFtePl0qKSo+KSg/IXVsKCBbXj5dKikqPikoPyFvbCggW14+XSopKj4pKD8hbGkoIFtePl0qKSo+KSg/IXAoIFtePl0qKSo+KSg/IWRpdiggW14+XSopKj4pKD8hdGQoIFtePl0qKSo+KSg/IWJyKCBbXj5dKikqPilbXj5cXC9dKVtePl0qPi9naSwgXCJcIik7XG5cblx0Ly8gcmVtb3ZlIG9yIHJlcGxhY2UgaW1hZ2VzIC0gcmVwbGFjZW1lbnQgdGV4dHMgd2l0aCA8PiB0YWdzIHdpbGwgYmUgcmVtb3ZlZCBhbHNvLCBpZiBub3QgaW50ZW50aW9uYWwsIHRyeSB0byB1c2Ugb3RoZXIgbm90YXRpb25cblx0dG1wID0gdG1wLnJlcGxhY2UoLzxpbWcoW14+XSopPi9naSwgZnVuY3Rpb24oc3RyLCBpbUF0dHJzKSB7XG5cdFx0dmFyIGltU3JjID0gXCJcIjtcblx0XHR2YXIgaW1BbHQgPSBcIlwiO1xuXHRcdHZhciBpbVNyY1Jlc3VsdCA9ICgvc3JjPVwiKFteXCJdKilcIi9pKS5leGVjKGltQXR0cnMpO1xuXHRcdHZhciBpbUFsdFJlc3VsdCA9ICgvYWx0PVwiKFteXCJdKilcIi9pKS5leGVjKGltQXR0cnMpO1xuXHRcdGlmKGltU3JjUmVzdWx0ICE9PSBudWxsKXtcblx0XHRcdGltU3JjID0gaW1TcmNSZXN1bHRbMV07XG5cdFx0fVxuXHRcdGlmKGltQWx0UmVzdWx0ICE9PSBudWxsKXtcblx0XHRcdGltQWx0ID0gaW1BbHRSZXN1bHRbMV07XG5cdFx0fVxuXHRcdGlmKHR5cGVvZihpbWdQcm9jZXNzKSA9PT0gXCJmdW5jdGlvblwiKSB7XG5cdFx0XHRyZXR1cm4gaW1nUHJvY2VzcyhpbVNyYywgaW1BbHQpO1xuXHRcdH1cblx0XHRpZihpbUFsdCA9PT0gXCJcIil7XG5cdFx0XHRyZXR1cm4gXCIhW2ltYWdlXSAoXCIrIGltU3JjICsgXCIpXCI7XG5cdFx0fVxuXHRcdHJldHVybiBcIiFbXCIgKyBpbUFsdCtcIl0gKFwiKyBpbVNyYyArIFwiKVwiO1xuXHR9KTtcblxuXG5cdGZ1bmN0aW9uIGNyZWF0ZUxpc3RSZXBsYWNlQ2IoKSB7XG5cdFx0cmV0dXJuIGZ1bmN0aW9uKG1hdGNoLCBsaXN0VHlwZSwgbGlzdEF0dHJpYnV0ZXMsIGxpc3RCb2R5KSB7XG5cdFx0XHR2YXIgbGlJbmRleCA9IDA7XG5cdFx0XHRpZihsaXN0QXR0cmlidXRlcyAmJiAvc3RhcnQ9XCIoWzAtOV0rKVwiL2kudGVzdChsaXN0QXR0cmlidXRlcykpIHtcblx0XHRcdFx0bGlJbmRleCA9ICgvc3RhcnQ9XCIoWzAtOV0rKVwiL2kuZXhlYyhsaXN0QXR0cmlidXRlcylbMV0pLTE7XG5cdFx0XHR9XG5cdFx0XHR2YXIgcGxhaW5MaXN0SXRlbSA9IFwiPHA+XCIgKyBsaXN0Qm9keS5yZXBsYWNlKC88bGlbXj5dKj4oKCg/ITxsaVtePl0qPikoPyE8XFwvbGk+KS4pKik8XFwvbGk+L2dpLCBmdW5jdGlvbihzdHIsIGxpc3RJdGVtKSB7XG5cdFx0XHRcdHZhciBhY3RTdWJJbmRleCA9IDA7XG5cdFx0XHRcdHZhciBwbGFpbkxpc3RMaW5lID0gbGlzdEl0ZW0ucmVwbGFjZSgvKF58KDxiciBcXC8+KSkoPyE8cD4pL2dpLCBmdW5jdGlvbigpe1xuXHRcdFx0XHRcdGlmKGxpc3RUeXBlID09PSBcIm9cIiAmJiBhY3RTdWJJbmRleCA9PT0gMCl7XG5cdFx0XHRcdFx0XHRsaUluZGV4ICs9IDE7XG5cdFx0XHRcdFx0XHRhY3RTdWJJbmRleCArPSAxO1xuXHRcdFx0XHRcdFx0cmV0dXJuIFwiPGJyIC8+XCIgKyBsaUluZGV4ICsgcG9wdWxhdGVDaGFyKG9JbmRlbnRpb25DaGFyLCBsaXN0SW5kZW50aW9uVGFicy0oU3RyaW5nKGxpSW5kZXgpLmxlbmd0aCkpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRyZXR1cm4gXCI8YnIgLz5cIiArIHVJbmRlbnRpb247XG5cdFx0XHRcdH0pO1xuXHRcdFx0XHRyZXR1cm4gcGxhaW5MaXN0TGluZTtcblx0XHRcdH0pK1wiPC9wPlwiO1xuXHRcdFx0cmV0dXJuIHBsYWluTGlzdEl0ZW07XG5cdFx0fTtcblx0fVxuXG5cdC8vIGhhbmRsZSBsaXN0c1xuXHRpZihsaXN0U3R5bGUgPT09IFwibGluZWJyZWFrXCIpe1xuXHRcdHRtcCA9IHRtcC5yZXBsYWNlKC88XFwvP3VsW14+XSo+fDxcXC8/b2xbXj5dKj58PFxcLz9saVtePl0qPi9naSwgXCJcXG5cIik7XG5cdH1cblx0ZWxzZSBpZihsaXN0U3R5bGUgPT09IFwiaW5kZW50aW9uXCIpe1xuXHRcdHdoaWxlKCAvPChvfHUpbFtePl0qPiguKik8XFwvXFwxbD4vZ2kudGVzdCh0bXApKXtcblx0XHRcdHRtcCA9IHRtcC5yZXBsYWNlKC88KG98dSlsKFtePl0qKT4oKCg/ITwob3x1KWxbXj5dKj4pKD8hPFxcLyhvfHUpbD4pLikqKTxcXC9cXDFsPi9naSwgY3JlYXRlTGlzdFJlcGxhY2VDYigpKTtcblx0XHR9XG5cdH1cblxuXHQvLyBoYW5kbGUgaGVhZGluZ3Ncblx0aWYoaGVhZGluZ1N0eWxlID09PSBcImxpbmVicmVha1wiKSB7XG5cdFx0dG1wID0gdG1wLnJlcGxhY2UoLzxoKFsxLTZdKVtePl0qPihbXjxdKik8XFwvaFxcMT4vZ2ksIFwiXFxuJDJcXG5cIik7XG5cdH1cblx0ZWxzZSBpZihoZWFkaW5nU3R5bGUgPT09IFwidW5kZXJsaW5lXCIpIHtcblx0XHR0bXAgPSB0bXAucmVwbGFjZSgvPGgxW14+XSo+KCgoPyE8XFwvaDE+KS4pKik8XFwvaDE+L2dpLCBmdW5jdGlvbihzdHIsIHAxKSB7XG5cdFx0XHRyZXR1cm4gXCJcXG4mbmJzcDtcXG5cIiArIHAxICsgXCJcXG5cIiArIHBvcHVsYXRlQ2hhcihcIj1cIiwgcDEubGVuZ3RoKSArIFwiXFxuJm5ic3A7XFxuXCI7XG5cdFx0fSk7XG5cdFx0dG1wID0gdG1wLnJlcGxhY2UoLzxoMltePl0qPigoKD8hPFxcL2gyPikuKSopPFxcL2gyPi9naSwgZnVuY3Rpb24oc3RyLCBwMSkge1xuXHRcdFx0cmV0dXJuIFwiXFxuJm5ic3A7XFxuXCIgKyBwMSArIFwiXFxuXCIgKyBwb3B1bGF0ZUNoYXIoXCItXCIsIHAxLmxlbmd0aCkgKyBcIlxcbiZuYnNwO1xcblwiO1xuXHRcdH0pO1xuXHRcdHRtcCA9IHRtcC5yZXBsYWNlKC88aChbMy02XSlbXj5dKj4oKCg/ITxcXC9oXFwxPikuKSopPFxcL2hcXDE+L2dpLCBmdW5jdGlvbihzdHIsIHAxLCBwMikge1xuXHRcdFx0cmV0dXJuIFwiXFxuJm5ic3A7XFxuXCIgKyBwMiArIFwiXFxuJm5ic3A7XFxuXCI7XG5cdFx0fSk7XG5cdH1cblx0ZWxzZSBpZihoZWFkaW5nU3R5bGUgPT09IFwiaGFzaGlmeVwiKSB7XG5cdFx0dG1wID0gdG1wLnJlcGxhY2UoLzxoKFsxLTZdKVtePl0qPihbXjxdKik8XFwvaFxcMT4vZ2ksIGZ1bmN0aW9uKHN0ciwgcDEsIHAyKSB7XG5cdFx0XHRyZXR1cm4gXCJcXG4mbmJzcDtcXG5cIiArIHBvcHVsYXRlQ2hhcihcIiNcIiwgcDEpICsgXCIgXCIgKyBwMiArIFwiXFxuJm5ic3A7XFxuXCI7XG5cdFx0fSk7XG5cdH1cblxuXHQvLyByZXBsYWNlIDxicj5zLCA8dGQ+cywgPGRpdnM+IGFuZCA8cD5zIHdpdGggbGluZWJyZWFrc1xuXHR0bXAgPSB0bXAucmVwbGFjZSgvPGJyKCBbXj5dKikqPnw8cCggW14+XSopKj58PFxcL3AoIFtePl0qKSo+fDxkaXYoIFtePl0qKSo+fDxcXC9kaXYoIFtePl0qKSo+fDx0ZCggW14+XSopKj58PFxcL3RkKCBbXj5dKikqPi9naSwgXCJcXG5cIik7XG5cblx0Ly8gcmVwbGFjZSA8YSBocmVmPmI8YT4gbGlua3Mgd2l0aCBiIChocmVmKSBvciBhcyBkZXNjcmliZWQgaW4gdGhlIGxpbmtQcm9jZXNzIGZ1bmN0aW9uXG5cdHRtcCA9IHRtcC5yZXBsYWNlKC88YVtePl0qaHJlZj1cIihbXlwiXSopXCJbXj5dKj4oW148XSspPFxcL2FbXj5dKj4vZ2ksIGZ1bmN0aW9uKHN0ciwgaHJlZiwgbGlua1RleHQpIHtcblx0XHRpZih0eXBlb2YgbGlua1Byb2Nlc3MgPT09IFwiZnVuY3Rpb25cIikge1xuXHRcdFx0cmV0dXJuIGxpbmtQcm9jZXNzKGhyZWYsIGxpbmtUZXh0KTtcblx0XHR9XG5cdFx0cmV0dXJuIFwiIFtcIiArIGxpbmtUZXh0K1wiXSAoXCIrIGhyZWYgKyBcIikgXCI7XG5cdH0pO1xuXG5cdC8vIHJlbW92ZSB3aGl0ZXNwYWNlIGZyb20gZW1wdHkgbGluZXMgZXhjbHVkaW5nIG5ic3Bcblx0dG1wID0gdG1wLnJlcGxhY2UoL1xcblsgXFx0XFxmXSovZ2ksIFwiXFxuXCIpO1xuXG5cdC8vIHJlbW92ZSBkdXBsaWNhdGVkIGVtcHR5IGxpbmVzXG5cdHRtcCA9IHRtcC5yZXBsYWNlKC9cXG5cXG4rL2dpLCBcIlxcblwiKTtcblxuXHQvLyByZW1vdmUgZHVwbGljYXRlZCBzcGFjZXMgaW5jbHVkaW5nIG5vbiBicmFraW5nIHNwYWNlc1xuXHR0bXAgPSB0bXAucmVwbGFjZSgvKCB8Jm5ic3A7fFxcdCkrL2dpLCBcIiBcIik7XG5cblx0Ly8gcmVtb3ZlIGxpbmUgc3RhcnRlciBzcGFjZXNcblx0dG1wID0gdG1wLnJlcGxhY2UoL1xcbiArL2dpLCBcIlxcblwiKTtcblxuXHQvLyByZW1vdmUgY29udGVudCBzdGFydGVyIHNwYWNlc1xuXHR0bXAgPSB0bXAucmVwbGFjZSgvXiArL2dpLCBcIlwiKTtcblxuXHQvLyByZW1vdmUgZmlyc3QgZW1wdHkgbGluZVxuXHR3aGlsZSh0bXAuaW5kZXhPZihcIlxcblwiKSA9PT0gMCl7XG5cdFx0dG1wID0gdG1wLnN1YnN0cmluZygxKTtcblx0fVxuXG5cdC8vIHB1dCBhIG5ldyBsaW5lIGF0IHRoZSBlbmRcblx0aWYodG1wLmxlbmd0aCA9PT0gMCB8fCB0bXAubGFzdEluZGV4T2YoXCJcXG5cIikgIT09IHRtcC5sZW5ndGgtMSl7XG5cdFx0dG1wICs9IFwiXFxuXCI7XG5cdH1cblxuXHRyZXR1cm4gdG1wO1xufVxuXG5cbihmdW5jdGlvbiAobmFtZSwgZGVmaW5pdGlvbil7XG5cdGlmICh0aGlzICYmIHR5cGVvZiB0aGlzLmRlZmluZSA9PT0gXCJmdW5jdGlvblwiKXsgLy8gQU1EXG5cdFx0dGhpcy5kZWZpbmUoZGVmaW5pdGlvbik7XG5cdH0gZWxzZSBpZiAodHlwZW9mIG1vZHVsZSAhPT0gXCJ1bmRlZmluZWRcIiAmJiBtb2R1bGUuZXhwb3J0cykgeyAvLyBOb2RlLmpzXG5cdFx0bW9kdWxlLmV4cG9ydHMgPSBkZWZpbml0aW9uKCk7XG5cdH0gZWxzZSB7IC8vIEJyb3dzZXJcblx0XHR2YXIgdGhlTW9kdWxlID0gZGVmaW5pdGlvbigpO1xuXHRcdHZhciBnbG9iYWwgPSB0aGlzO1xuXHRcdHZhciBvbGQgPSBnbG9iYWxbbmFtZV07XG5cdFx0dGhlTW9kdWxlLm5vQ29uZmxpY3QgPSBmdW5jdGlvbiAoKSB7XG5cdFx0XHRnbG9iYWxbbmFtZV0gPSBvbGQ7XG5cdFx0XHRyZXR1cm4gdGhlTW9kdWxlO1xuXHRcdH07XG5cdFx0Z2xvYmFsW25hbWVdID0gdGhlTW9kdWxlO1xuXHR9XG59KShcImNyZWF0ZVRleHRWZXJzaW9uXCIsIGZ1bmN0aW9uICgpIHtcblx0cmV0dXJuIGh0bWxUb1BsYWluVGV4dDtcbn0pO1xuIl19 198 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var gulp = require("gulp"); 4 | var createSuperGulp = require("edm-supergulp"); 5 | 6 | var superGulp = createSuperGulp({ 7 | gulp: gulp 8 | }); 9 | 10 | var packageJson = require("./package.json"); 11 | 12 | var jsFiles = [ 13 | "./*.js", 14 | "./src/**/*.js", 15 | "./spec/**/*.js", 16 | "./examples/*.js" 17 | ]; 18 | 19 | var jsonFiles = [ 20 | ".jshintrc", 21 | ".jscsrc", 22 | "./package.json", 23 | "./src/**/*.json", 24 | "./spec/**/*.json", 25 | "./examples/*.json" 26 | ]; 27 | 28 | var specFiles = [ 29 | "spec/**/*Spec.js" 30 | ]; 31 | 32 | var sourceFiles = [ 33 | "src/**/*.js" 34 | ]; 35 | 36 | superGulp.taskTemplates.initFrontendTasks({ 37 | packageJson: packageJson, 38 | coverage: 70, 39 | files: { 40 | js: jsFiles, 41 | json: jsonFiles, 42 | spec: specFiles, 43 | source: sourceFiles 44 | }, 45 | tasks: { 46 | copy: { 47 | common: [ 48 | { files: "./img/*", dest: "./dist/img/"}, 49 | { files: "./lib/**/*", dest: "./dist/lib/"}, 50 | { files: "./example/fonts/*", dest: "./dist/lib/fonts/"}, 51 | { files: "./css/*", dest: "./dist/"} 52 | ], 53 | dev: [ 54 | { files: "./example/css/*", dest: "./dist/"}, 55 | { files: "./example/index.html", dest: "./dist/"}, 56 | { files: "./example/img/*", dest: "./dist/img/"}, 57 | ] 58 | }, 59 | js: { 60 | common: [ 61 | { 62 | entries: ["./src/textversion.js"], 63 | outputFileName: "textversion.js", 64 | standaloneName: "textversion", 65 | destFolder: "./docs/js/" 66 | } 67 | ] 68 | } 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textversionjs", 3 | "version": "1.1.3", 4 | "description": "A tool for generating the text version of an html email.", 5 | "main": "src/textversion.js", 6 | "scripts": { 7 | "test": "gulp test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/EDMdesigner/textversionjs.git" 12 | }, 13 | "keywords": [ 14 | "email", 15 | "html", 16 | "text", 17 | "version", 18 | "plaintext" 19 | ], 20 | "author": "EDMdesigner.com", 21 | "maintainers": [ 22 | { 23 | "name": "Attila Stubendek", 24 | "email": "stubendek.attila@gmail.com" 25 | } 26 | ], 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/EDMdesigner/textversionjs/issues" 30 | }, 31 | "homepage": "https://github.com/EDMdesigner/textversionjs#readme", 32 | "devDependencies": { 33 | "edm-supergulp": "0.2.8", 34 | "gulp": "^3.9.1" 35 | } 36 | } -------------------------------------------------------------------------------- /spec/textversionsSpec.js: -------------------------------------------------------------------------------- 1 | var textVerionsCore = require("../src/textversion"); 2 | 3 | describe("Empty output on empty content input", function(){ 4 | it("returns empty", function(){ 5 | expect(textVerionsCore("")).toEqual("\n"); 6 | }); 7 | it("empty body", function(){ 8 | expect(textVerionsCore("Foo")).toEqual("\n"); 9 | }); 10 | it("no text just tags", function(){ 11 | expect(textVerionsCore("

    ")).toEqual("\n"); 12 | }); 13 | }); 14 | 15 | describe("empty html tags", function(){ 16 | it("text should only remain", function(){ 17 | expect(textVerionsCore("

    Lorem ipsum

    dolorem
    sic amet

    ")) 18 | .toEqual("Lorem ipsum dolorem sic amet\n"); 19 | }); 20 | }); 21 | 22 | describe("row braking stuff", function(){ 23 | it("simple row brake", function(){ 24 | expect(textVerionsCore("Lorem ipsum
    dolorem
    sic amet")) 25 | .toEqual("Lorem ipsum\ndolorem \nsic amet\n"); 26 | }); 27 | it("simple paragraph", function(){ 28 | expect(textVerionsCore("

    Lorem ipsum dolorem sic amet

    ")) 29 | .toEqual("Lorem ipsum dolorem sic amet\n"); 30 | }); 31 | it("simple double paragraph", function(){ 32 | expect(textVerionsCore("

    Lorem ipsum

    dolorem sic amet

    ")) 33 | .toEqual("Lorem ipsum\ndolorem sic amet\n"); 34 | }); 35 | it("simple div", function(){ 36 | expect(textVerionsCore("
    Lorem ipsum dolorem sic amet
    ")) 37 | .toEqual("Lorem ipsum dolorem sic amet\n"); 38 | }); 39 | it("div, p and br together", function(){ 40 | expect(textVerionsCore("
    Lorem
    ipsum

    dolor sic amet,
    consectetur adipiscing elit, sed do

    eiusmod tempor
    incididunt ut labore
    ")) 41 | .toEqual("Lorem \nipsum\ndolor sic amet,\nconsectetur adipiscing elit, sed do \neiusmod tempor \nincididunt ut labore\n"); 42 | }); 43 | }); 44 | 45 | describe("link replace", function(){ 46 | it("simple link replacement", function(){ 47 | expect(textVerionsCore("Lorem
    ipsum dolorem sic amet")) 48 | .toEqual("Lorem [ipsum] (here) dolorem sic amet\n"); 49 | }); 50 | it("link replacement with function", function(){ 51 | expect(textVerionsCore("Lorem ipsum dolorem sic amet", {linkProcess: function(href, linkText){ 52 | return " {" + linkText+"} ["+ href + "] "; 53 | }})) 54 | .toEqual("Lorem {ipsum} [here] dolorem sic amet\n"); 55 | }); 56 | it("link replacement with function to discard link href", function(){ 57 | expect(textVerionsCore("Lorem ipsum dolorem sic amet", {linkProcess: function(href, linkText){ 58 | return linkText; 59 | }})) 60 | .toEqual("Lorem ipsum dolorem sic amet\n"); 61 | }); 62 | it("link replacement with function to discard link href and put braces", function(){ 63 | expect(textVerionsCore("Lorem ipsum dolorem sic amet", {linkProcess: function(href, linkText){ 64 | return "[" + linkText + "]"; 65 | }})) 66 | .toEqual("Lorem [ipsum] dolorem sic amet\n"); 67 | }); 68 | }); 69 | 70 | describe("image replace", function(){ 71 | it("simple image replacement", function(){ 72 | expect(textVerionsCore("Lorem \"ipsum\" dolorem sic amet")) 73 | .toEqual("Lorem ![ipsum] (imagelink) dolorem sic amet\n"); 74 | }); 75 | it("simple image replacement if no alt is present", function(){ 76 | expect(textVerionsCore("Lorem \"\" dolorem sic amet")) 77 | .toEqual("Lorem ![image] (imagelink) dolorem sic amet\n"); 78 | }); 79 | it("image replacement with function", function(){ 80 | expect(textVerionsCore("Lorem \"ipsum\" dolorem sic amet", {imgProcess: function(src, imAlt){ 81 | return " {" + imAlt+"} ["+ src + "] "; 82 | }})) 83 | .toEqual("Lorem {ipsum} [imagelink] dolorem sic amet\n"); 84 | }); 85 | it("image replacement with function to discard source", function(){ 86 | expect(textVerionsCore("Lorem \"ipsum\" dolorem sic amet", {imgProcess: function(src, imAlt){ 87 | return imAlt; 88 | }})) 89 | .toEqual("Lorem ipsum dolorem sic amet\n"); 90 | }); 91 | it("image replacement with function to discard cource and put braces", function(){ 92 | expect(textVerionsCore("Lorem \"ipsum\" dolorem sic amet", {imgProcess: function(src, imAlt){ 93 | return "[" + imAlt + "]"; 94 | }})) 95 | .toEqual("Lorem [ipsum] dolorem sic amet\n"); 96 | }); 97 | }); 98 | 99 | describe("headings", function(){ 100 | it("simple heading with underline", function(){ 101 | expect(textVerionsCore("

    Lorem

    ipsum

    dolorem

    sic

    amet

    sic amet")) 102 | .toEqual("Lorem\n=====\n\nipsum \n\ndolorem\n-------\n\nsic \n\namet\n\nsic amet\n"); 103 | }); 104 | it("heading with underline other tags within", function(){ 105 | expect(textVerionsCore("

    Lorem

    ipsum

    dolorem

    sic

    amet

    sic amet")) 106 | .toEqual("Lorem\n=====\n\nipsum \n\ndolorem\n-------\n\nsic \n\namet\n\nsic amet\n"); 107 | }); 108 | it("heading removal with option linebreak", function(){ 109 | expect(textVerionsCore("

    Lorem

    ipsum dolorem sic amet", {headingStyle: "linebreak"})) 110 | .toEqual("Lorem\nipsum dolorem sic amet\n"); 111 | }); 112 | it("heading removal with option hashify", function(){ 113 | expect(textVerionsCore("

    Lorem

    ipsum

    dolorem

    sic

    amet

    sic amet", {headingStyle: "hashify"})) 114 | .toEqual("# Lorem\n\nipsum \n\n## dolorem\n\nsic \n\n#### amet\n\nsic amet\n"); 115 | }); 116 | }); 117 | 118 | describe("lists", function(){ 119 | it("ulist removal with option linebreak", function(){ 120 | expect(textVerionsCore("
    • Lorem
    • ipsum
    dolorem sic amet", {listStyle: "linebreak"})) 121 | .toEqual("Lorem\nipsum\ndolorem sic amet\n"); 122 | }); 123 | it("simple ulist removal", function(){ 124 | expect(textVerionsCore("
    • Lorem
    • ipsum
    dolorem sic amet")) 125 | .toEqual("---Lorem\n---ipsum\ndolorem sic amet\n"); 126 | }); 127 | it("ulist removal with option indention and other styles", function(){ 128 | expect(textVerionsCore("
    • Lorem
    • ipsum
    dolorem sic amet", {listStyle: "indention", uIndentionChar: "=", listIndentionTabs: "4"})) 129 | .toEqual("====Lorem\n====ipsum\ndolorem sic amet\n"); 130 | }); 131 | it("simple olist removal", function(){ 132 | expect(textVerionsCore("
    1. Lorem
    2. ipsum
    dolorem sic amet")) 133 | .toEqual("1--Lorem\n2--ipsum\ndolorem sic amet\n"); 134 | }); 135 | it("olist removal with option linebreak", function(){ 136 | expect(textVerionsCore("
    1. Lorem
    2. ipsum
    dolorem sic amet", {listStyle: "linebreak"})) 137 | .toEqual("Lorem\nipsum\ndolorem sic amet\n"); 138 | }); 139 | it("olist removal with starting value and option indention and other styles", function(){ 140 | expect(textVerionsCore("
    1. Lorem
    2. ipsum
    dolorem sic amet", {listStyle: "indention", oIndentionChar: "=", listIndentionTabs: "4"})) 141 | .toEqual("31==Lorem\n32==ipsum\ndolorem sic amet\n"); 142 | }); 143 | it("combined ulist and olist removal with starting value and option indention and other styles", function(){ 144 | expect(textVerionsCore("
    1. Lorem
      • ipsum
      • dolorem
    sic amet", {listStyle: "indention", oIndentionChar: "=", uIndentionChar: "=", listIndentionTabs: "2"})) 145 | .toEqual("3=Lorem\n4===ipsum\n====dolorem\nsic amet\n"); 146 | }); 147 | }); 148 | 149 | describe("remove full blocks when needed", function(){ 150 | it("remove body scripts", function(){ 151 | expect(textVerionsCore("\nLorem ipsum dolorem sic amet")) 152 | .toEqual("Lorem ipsum dolorem sic amet\n"); 153 | }); 154 | it("remove body styles", function(){ 155 | expect(textVerionsCore("\nLorem ipsum dolorem sic amet")) 156 | .toEqual("Lorem ipsum dolorem sic amet\n"); 157 | }); 158 | it("remove body comments", function(){ 159 | expect(textVerionsCore("Lorem ipsum dolorem sic amet")) 160 | .toEqual("Lorem ipsum dolorem sic amet\n"); 161 | }); 162 | }); 163 | 164 | describe("pasting from editors", function(){ 165 | it("google doc styling meta tag", function(){ 166 | expect(textVerionsCore("line 1")) 167 | .toEqual("line 1\n"); 168 | }); 169 | it("ms word styling ", function(){ 170 | expect(textVerionsCore("Line 2")) 171 | .toEqual("Line 2\n"); 172 | }); 173 | it("fix special tag endings", function(){ 174 | expect(textVerionsCore("A")) 175 | .toEqual("A\n"); 176 | }); 177 | }); 178 | -------------------------------------------------------------------------------- /src/textversion.js: -------------------------------------------------------------------------------- 1 | var populateChar = function(ch, amount){ 2 | var result = ""; 3 | for(var i=0; i tags including the tag itself 55 | const bodyEndMatch = tmp.match(/<\/body>/i); 56 | if (bodyEndMatch) { 57 | tmp = tmp.substring(0, bodyEndMatch.index); 58 | } 59 | const bodyStartMatch = tmp.match(/]*>/i); 60 | if (bodyStartMatch) { 61 | tmp = tmp.substring(bodyStartMatch.index + bodyStartMatch[0].length, tmp.length); 62 | } 63 | 64 | // remove inbody scripts and styles 65 | tmp = tmp.replace(/<(script|style)( [^>]*)*>((?!<\/\1( [^>]*)*>).)*<\/\1>/gi, ""); 66 | 67 | // remove all tags except that are being handled separately 68 | tmp = tmp.replace(/<(\/)?((?!h[1-6]( [^>]*)*>)(?!img( [^>]*)*>)(?!a( [^>]*)*>)(?!ul( [^>]*)*>)(?!ol( [^>]*)*>)(?!li( [^>]*)*>)(?!p( [^>]*)*>)(?!div( [^>]*)*>)(?!td( [^>]*)*>)(?!br( [^>]*)*>)[^>\/])[^<>]*>/gi, ""); 69 | 70 | // remove or replace images - replacement texts with <> tags will be removed also, if not intentional, try to use other notation 71 | tmp = tmp.replace(/]*)>/gi, function(str, imAttrs) { 72 | var imSrc = ""; 73 | var imAlt = ""; 74 | var imSrcResult = (/src="([^"]*)"/i).exec(imAttrs); 75 | var imAltResult = (/alt="([^"]*)"/i).exec(imAttrs); 76 | if(imSrcResult !== null){ 77 | imSrc = imSrcResult[1]; 78 | } 79 | if(imAltResult !== null){ 80 | imAlt = imAltResult[1]; 81 | } 82 | if(typeof(imgProcess) === "function") { 83 | return imgProcess(imSrc, imAlt); 84 | } 85 | if(imAlt === ""){ 86 | return "![image] ("+ imSrc + ")"; 87 | } 88 | return "![" + imAlt+"] ("+ imSrc + ")"; 89 | }); 90 | 91 | 92 | function createListReplaceCb() { 93 | return function(match, listType, listAttributes, listBody) { 94 | var liIndex = 0; 95 | if(listAttributes && /start="([0-9]+)"/i.test(listAttributes)) { 96 | liIndex = (/start="([0-9]+)"/i.exec(listAttributes)[1])-1; 97 | } 98 | var plainListItem = "

    " + listBody.replace(/]*>(((?!]*>)(?!<\/li>).)*)<\/li>/gi, function(str, listItem) { 99 | var actSubIndex = 0; 100 | var plainListLine = listItem.replace(/(^|(
    ))(?!

    )/gi, function(){ 101 | if(listType === "o" && actSubIndex === 0){ 102 | liIndex += 1; 103 | actSubIndex += 1; 104 | return "
    " + liIndex + populateChar(oIndentionChar, listIndentionTabs-(String(liIndex).length)); 105 | } 106 | return "
    " + uIndention; 107 | }); 108 | return plainListLine; 109 | })+"

    "; 110 | return plainListItem; 111 | }; 112 | } 113 | 114 | // handle lists 115 | if(listStyle === "linebreak"){ 116 | tmp = tmp.replace(/<\/?ul[^>]*>|<\/?ol[^>]*>|<\/?li[^>]*>/gi, "\n"); 117 | } 118 | else if(listStyle === "indention"){ 119 | while( /<(o|u)l[^>]*>(.*)<\/\1l>/gi.test(tmp)){ 120 | tmp = tmp.replace(/<(o|u)l([^>]*)>(((?!<(o|u)l[^>]*>)(?!<\/(o|u)l>).)*)<\/\1l>/gi, createListReplaceCb()); 121 | } 122 | } 123 | 124 | // handle headings 125 | if(headingStyle === "linebreak") { 126 | tmp = tmp.replace(/]*>([^<]*)<\/h\1>/gi, "\n$2\n"); 127 | } 128 | else if(headingStyle === "underline") { 129 | tmp = tmp.replace(/]*>(((?!<\/h1>).)*)<\/h1>/gi, function(str, p1) { 130 | return "\n \n" + p1 + "\n" + populateChar("=", p1.length) + "\n \n"; 131 | }); 132 | tmp = tmp.replace(/]*>(((?!<\/h2>).)*)<\/h2>/gi, function(str, p1) { 133 | return "\n \n" + p1 + "\n" + populateChar("-", p1.length) + "\n \n"; 134 | }); 135 | tmp = tmp.replace(/]*>(((?!<\/h\1>).)*)<\/h\1>/gi, function(str, p1, p2) { 136 | return "\n \n" + p2 + "\n \n"; 137 | }); 138 | } 139 | else if(headingStyle === "hashify") { 140 | tmp = tmp.replace(/]*>([^<]*)<\/h\1>/gi, function(str, p1, p2) { 141 | return "\n \n" + populateChar("#", p1) + " " + p2 + "\n \n"; 142 | }); 143 | } 144 | 145 | // replace
    s, s, and

    s with linebreaks 146 | tmp = tmp.replace(/]*)*>|]*)*>|<\/p( [^>]*)*>|]*)*>|<\/div( [^>]*)*>|]*)*>|<\/td( [^>]*)*>/gi, "\n"); 147 | 148 | // replace b links with b (href) or as described in the linkProcess function 149 | tmp = tmp.replace(/]*href="([^"]*)"[^>]*>([^<]+)<\/a[^>]*>/gi, function(str, href, linkText) { 150 | if(typeof linkProcess === "function") { 151 | return linkProcess(href, linkText); 152 | } 153 | return " [" + linkText+"] ("+ href + ") "; 154 | }); 155 | 156 | // remove whitespace from empty lines excluding nbsp 157 | tmp = tmp.replace(/\n[ \t\f]*/gi, "\n"); 158 | 159 | // remove duplicated empty lines 160 | tmp = tmp.replace(/\n\n+/gi, "\n"); 161 | 162 | if (keepNbsps) { 163 | // remove duplicated spaces including non braking spaces 164 | tmp = tmp.replace(/( |\t)+/gi, " "); 165 | tmp = tmp.replace(/ /gi, " "); 166 | } else { 167 | // remove duplicated spaces including non braking spaces 168 | tmp = tmp.replace(/( | |\t)+/gi, " "); 169 | } 170 | 171 | // remove line starter spaces 172 | tmp = tmp.replace(/\n +/gi, "\n"); 173 | 174 | // remove content starter spaces 175 | tmp = tmp.replace(/^ +/gi, ""); 176 | 177 | // remove first empty line 178 | while(tmp.indexOf("\n") === 0){ 179 | tmp = tmp.substring(1); 180 | } 181 | 182 | // put a new line at the end 183 | if(tmp.length === 0 || tmp.lastIndexOf("\n") !== tmp.length-1){ 184 | tmp += "\n"; 185 | } 186 | 187 | return tmp; 188 | } 189 | 190 | 191 | (function (name, definition){ 192 | if (this && typeof this.define === "function"){ // AMD 193 | this.define(definition); 194 | } else if (typeof module !== "undefined" && module.exports) { // Node.js 195 | module.exports = definition(); 196 | } else { // Browser 197 | var theModule = definition(); 198 | var global = this; 199 | var old = global[name]; 200 | theModule.noConflict = function () { 201 | global[name] = old; 202 | return theModule; 203 | }; 204 | global[name] = theModule; 205 | } 206 | })("createTextVersion", function () { 207 | return htmlToPlainText; 208 | }); 209 | --------------------------------------------------------------------------------