From ffee1c2969203ae77818a260c8e6321ccbb9b8ec Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Wed, 6 Dec 2023 08:38:14 +0200 Subject: [PATCH] Rewrite to ESM --- .eslintignore | 3 - .eslintrc.yml | 158 ++----------- .github/dependabot.yml | 13 ++ .github/workflows/ci.yml | 34 +++ .gitignore | 2 +- .npmignore | 6 - .travis.yml | 6 - CHANGELOG.md | 7 + Makefile | 39 ---- bower.json | 27 --- dist/markdown-it-footnote.js | 371 ------------------------------- dist/markdown-it-footnote.min.js | 1 - index.js | 367 ------------------------------ index.mjs | 353 +++++++++++++++++++++++++++++ package.json | 40 +++- rollup.config.mjs | 74 ++++++ test/cjs.js | 11 + test/{test.js => test.mjs} | 42 ++-- 18 files changed, 563 insertions(+), 991 deletions(-) delete mode 100644 .eslintignore create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 .npmignore delete mode 100644 .travis.yml delete mode 100644 Makefile delete mode 100644 bower.json delete mode 100644 dist/markdown-it-footnote.js delete mode 100644 dist/markdown-it-footnote.min.js delete mode 100644 index.js create mode 100644 index.mjs create mode 100644 rollup.config.mjs create mode 100644 test/cjs.js rename test/{test.js => test.mjs} (54%) diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c1290bb..0000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -coverage/ -dist/ -node_modules diff --git a/.eslintrc.yml b/.eslintrc.yml index 1ee633c..1cd8ca5 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,142 +1,22 @@ -env: - node: true - browser: false - es6: false +extends: standard -rules: - accessor-pairs: 2 - array-bracket-spacing: [ 2, "always", { "singleValue": true, "objectsInArrays": true, "arraysInArrays": true } ] - block-scoped-var: 2 - block-spacing: 2 - brace-style: [ 2, '1tbs', { allowSingleLine: true } ] - # Postponed - #callback-return: 2 - comma-dangle: 2 - comma-spacing: 2 - comma-style: 2 - computed-property-spacing: [ 2, never ] - consistent-this: [ 2, self ] - consistent-return: 2 - # ? change to multi - curly: [ 2, 'multi-line' ] - dot-notation: 2 - eol-last: 2 - eqeqeq: 2 - func-style: [ 2, declaration ] - # Postponed - #global-require: 2 - guard-for-in: 2 - handle-callback-err: 2 +overrides: + - + files: [ '*.mjs' ] + rules: + no-restricted-globals: [ 2, require, __dirname ] + - + files: [ 'test/**' ] + env: { mocha: true } + - + files: [ 'lib/**', 'index.mjs' ] + parserOptions: { ecmaVersion: 2015 } - indent: [ 2, 2, { VariableDeclarator: { var: 2, let: 2, const: 3 }, SwitchCase: 1 } ] +ignorePatterns: + - demo/ + - dist/ + - benchmark/extra/ - # key-spacing: [ 2, { "align": "value" } ] - keyword-spacing: 2 - linebreak-style: 2 - max-depth: [ 1, 6 ] - max-nested-callbacks: [ 1, 4 ] - # string can exceed 80 chars, but should not overflow github website :) - max-len: [ 2, 120, 1000 ] - new-cap: 2 - new-parens: 2 - # Postponed - #newline-after-var: 2 - no-alert: 2 - no-array-constructor: 2 - no-bitwise: 2 - no-caller: 2 - #no-case-declarations: 2 - no-catch-shadow: 2 - no-cond-assign: 2 - no-console: 1 - no-constant-condition: 2 - #no-control-regex: 2 - no-debugger: 2 - no-delete-var: 2 - no-div-regex: 2 - no-dupe-args: 2 - no-dupe-keys: 2 - no-duplicate-case: 2 - no-else-return: 2 - # Tend to drop - # no-empty: 1 - no-empty-character-class: 2 - no-empty-pattern: 2 - no-eq-null: 2 - no-eval: 2 - no-ex-assign: 2 - no-extend-native: 2 - no-extra-bind: 2 - no-extra-boolean-cast: 2 - no-extra-semi: 2 - no-fallthrough: 2 - no-floating-decimal: 2 - no-func-assign: 2 - # Postponed - #no-implicit-coercion: [2, { "boolean": true, "number": true, "string": true } ] - no-implied-eval: 2 - no-inner-declarations: 2 - no-invalid-regexp: 2 - no-irregular-whitespace: 2 - no-iterator: 2 - no-label-var: 2 - no-labels: 2 - no-lone-blocks: 2 - no-lonely-if: 2 - no-loop-func: 2 - no-mixed-requires: 2 - no-mixed-spaces-and-tabs: 2 - # Postponed - #no-native-reassign: 2 - no-negated-in-lhs: 2 - # Postponed - #no-nested-ternary: 2 - no-new: 2 - no-new-func: 2 - no-new-object: 2 - no-new-require: 2 - no-new-wrappers: 2 - no-obj-calls: 2 - no-octal: 2 - no-octal-escape: 2 - no-path-concat: 2 - no-proto: 2 - no-redeclare: 2 - # Postponed - #no-regex-spaces: 2 - no-return-assign: 2 - no-self-compare: 2 - no-sequences: 2 - no-shadow: 2 - no-shadow-restricted-names: 2 - no-sparse-arrays: 2 - no-trailing-spaces: 2 - no-undef: 2 - no-undef-init: 2 - no-undefined: 2 - no-unexpected-multiline: 2 - no-unreachable: 2 - no-unused-expressions: 2 - no-unused-vars: 2 - no-use-before-define: 2 - no-void: 2 - no-with: 2 - object-curly-spacing: [ 2, always, { "objectsInObjects": true, "arraysInObjects": true } ] - operator-assignment: 1 - # Postponed - #operator-linebreak: [ 2, after ] - semi: 2 - semi-spacing: 2 - space-before-function-paren: [ 2, { "anonymous": "always", "named": "never" } ] - space-in-parens: [ 2, never ] - space-infix-ops: 2 - space-unary-ops: 2 - # Postponed - #spaced-comment: [ 1, always, { exceptions: [ '/', '=' ] } ] - strict: [ 2, global ] - quotes: [ 2, single, avoid-escape ] - quote-props: [ 1, 'as-needed', { "keywords": true } ] - radix: 2 - use-isnan: 2 - valid-typeof: 2 - yoda: [ 2, never, { "exceptRange": true } ] +rules: + camelcase: 0 + no-multi-spaces: 0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b4bceaf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + + - package-ecosystem: npm + directory: / + schedule: + interval: daily + allow: + - dependency-type: production diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..241583b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * 3' + +jobs: + test: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [ '18' ] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - run: npm install + + - name: Test + run: npm test + + - name: Upload coverage report to coveralls.io + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 4c444c6..06c3eac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules/ coverage/ -*.log +dist/ diff --git a/.npmignore b/.npmignore deleted file mode 100644 index a329090..0000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -coverage/ -support/ -test/ -Makefile -.* -*.log diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 82e061e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: node_js -node_js: - - '6' - - node -script: make test-ci -sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e724fe..d5c1afb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +4.0.0 / 2023-12-06 +------------------ + +- Rewrite to ESM. +- Remove `dist/` from repo (build on package publish). + + 3.0.3 / 2021-05-20 ------------------ diff --git a/Makefile b/Makefile deleted file mode 100644 index d8333af..0000000 --- a/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -NPM_PACKAGE := $(shell node -e 'process.stdout.write(require("./package.json").name)') -NPM_VERSION := $(shell node -e 'process.stdout.write(require("./package.json").version)') - -TMP_PATH := /tmp/${NPM_PACKAGE}-$(shell date +%s) - -REMOTE_NAME ?= origin -REMOTE_REPO ?= $(shell git config --get remote.${REMOTE_NAME}.url) - -CURR_HEAD := $(firstword $(shell git show-ref --hash HEAD | cut -b -6) master) -GITHUB_PROJ := https://github.com//markdown-it/${NPM_PACKAGE} - - -lint: - ./node_modules/.bin/eslint . - -test: lint - ./node_modules/.bin/mocha -R spec - -coverage: - rm -rf coverage - ./node_modules/.bin/istanbul cover node_modules/.bin/_mocha - -test-ci: lint - istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage - -browserify: - rm -rf ./dist - mkdir dist - # Browserify - ( printf "/*! ${NPM_PACKAGE} ${NPM_VERSION} ${GITHUB_PROJ} @license MIT */" ; \ - ./node_modules/.bin/browserify ./ -s markdownitFootnote \ - ) > dist/markdown-it-footnote.js - # Minify - ./node_modules/.bin/terser dist/markdown-it-footnote.js -b beautify=false,ascii_only=true -c -m \ - --preamble "/*! ${NPM_PACKAGE} ${NPM_VERSION} ${GITHUB_PROJ} @license MIT */" \ - > dist/markdown-it-footnote.min.js - -.PHONY: lint test coverage -.SILENT: lint test diff --git a/bower.json b/bower.json deleted file mode 100644 index cfcf727..0000000 --- a/bower.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "markdown-it-footnote", - "main": "dist/markdown-it-footnote.js", - "homepage": "https://github.com/markdown-it/markdown-it-footnote", - "description": "Footnotes for markdown-it markdown parser.", - "keywords": [ - "markdown-it-plugin", - "markdown-it", - "markdown", - "footnotes" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "benchmark", - "bower_components", - "coverage", - "demo", - "docs", - "lib", - "node_modules", - "support", - "test", - "Makefile", - "index*" - ] -} diff --git a/dist/markdown-it-footnote.js b/dist/markdown-it-footnote.js deleted file mode 100644 index 39a1d7f..0000000 --- a/dist/markdown-it-footnote.js +++ /dev/null @@ -1,371 +0,0 @@ -/*! markdown-it-footnote 3.0.3 https://github.com//markdown-it/markdown-it-footnote @license MIT */(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.markdownitFootnote = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0) { - n += ':' + tokens[idx].meta.subId; - } - - return '[' + n + ']'; -} - -function render_footnote_ref(tokens, idx, options, env, slf) { - var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf); - var caption = slf.rules.footnote_caption(tokens, idx, options, env, slf); - var refid = id; - - if (tokens[idx].meta.subId > 0) { - refid += ':' + tokens[idx].meta.subId; - } - - return '' + caption + ''; -} - -function render_footnote_block_open(tokens, idx, options) { - return (options.xhtmlOut ? '
\n' : '
\n') + - '
\n' + - '
    \n'; -} - -function render_footnote_block_close() { - return '
\n
\n'; -} - -function render_footnote_open(tokens, idx, options, env, slf) { - var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf); - - if (tokens[idx].meta.subId > 0) { - id += ':' + tokens[idx].meta.subId; - } - - return '
  • '; -} - -function render_footnote_close() { - return '
  • \n'; -} - -function render_footnote_anchor(tokens, idx, options, env, slf) { - var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf); - - if (tokens[idx].meta.subId > 0) { - id += ':' + tokens[idx].meta.subId; - } - - /* ↩ with escape code to prevent display as Apple Emoji on iOS */ - return ' \u21a9\uFE0E'; -} - - -module.exports = function footnote_plugin(md) { - var parseLinkLabel = md.helpers.parseLinkLabel, - isSpace = md.utils.isSpace; - - md.renderer.rules.footnote_ref = render_footnote_ref; - md.renderer.rules.footnote_block_open = render_footnote_block_open; - md.renderer.rules.footnote_block_close = render_footnote_block_close; - md.renderer.rules.footnote_open = render_footnote_open; - md.renderer.rules.footnote_close = render_footnote_close; - md.renderer.rules.footnote_anchor = render_footnote_anchor; - - // helpers (only used in other rules, no tokens are attached to those) - md.renderer.rules.footnote_caption = render_footnote_caption; - md.renderer.rules.footnote_anchor_name = render_footnote_anchor_name; - - // Process footnote block definition - function footnote_def(state, startLine, endLine, silent) { - var oldBMark, oldTShift, oldSCount, oldParentType, pos, label, token, - initial, offset, ch, posAfterColon, - start = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; - - // line should be at least 5 chars - "[^x]:" - if (start + 4 > max) { return false; } - - if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } - if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; } - - for (pos = start + 2; pos < max; pos++) { - if (state.src.charCodeAt(pos) === 0x20) { return false; } - if (state.src.charCodeAt(pos) === 0x5D /* ] */) { - break; - } - } - - if (pos === start + 2) { return false; } // no empty footnote labels - if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 0x3A /* : */) { return false; } - if (silent) { return true; } - pos++; - - if (!state.env.footnotes) { state.env.footnotes = {}; } - if (!state.env.footnotes.refs) { state.env.footnotes.refs = {}; } - label = state.src.slice(start + 2, pos - 2); - state.env.footnotes.refs[':' + label] = -1; - - token = new state.Token('footnote_reference_open', '', 1); - token.meta = { label: label }; - token.level = state.level++; - state.tokens.push(token); - - oldBMark = state.bMarks[startLine]; - oldTShift = state.tShift[startLine]; - oldSCount = state.sCount[startLine]; - oldParentType = state.parentType; - - posAfterColon = pos; - initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); - - while (pos < max) { - ch = state.src.charCodeAt(pos); - - if (isSpace(ch)) { - if (ch === 0x09) { - offset += 4 - offset % 4; - } else { - offset++; - } - } else { - break; - } - - pos++; - } - - state.tShift[startLine] = pos - posAfterColon; - state.sCount[startLine] = offset - initial; - - state.bMarks[startLine] = posAfterColon; - state.blkIndent += 4; - state.parentType = 'footnote'; - - if (state.sCount[startLine] < state.blkIndent) { - state.sCount[startLine] += state.blkIndent; - } - - state.md.block.tokenize(state, startLine, endLine, true); - - state.parentType = oldParentType; - state.blkIndent -= 4; - state.tShift[startLine] = oldTShift; - state.sCount[startLine] = oldSCount; - state.bMarks[startLine] = oldBMark; - - token = new state.Token('footnote_reference_close', '', -1); - token.level = --state.level; - state.tokens.push(token); - - return true; - } - - // Process inline footnotes (^[...]) - function footnote_inline(state, silent) { - var labelStart, - labelEnd, - footnoteId, - token, - tokens, - max = state.posMax, - start = state.pos; - - if (start + 2 >= max) { return false; } - if (state.src.charCodeAt(start) !== 0x5E/* ^ */) { return false; } - if (state.src.charCodeAt(start + 1) !== 0x5B/* [ */) { return false; } - - labelStart = start + 2; - labelEnd = parseLinkLabel(state, start + 1); - - // parser failed to find ']', so it's not a valid note - if (labelEnd < 0) { return false; } - - // We found the end of the link, and know for a fact it's a valid link; - // so all that's left to do is to call tokenizer. - // - if (!silent) { - if (!state.env.footnotes) { state.env.footnotes = {}; } - if (!state.env.footnotes.list) { state.env.footnotes.list = []; } - footnoteId = state.env.footnotes.list.length; - - state.md.inline.parse( - state.src.slice(labelStart, labelEnd), - state.md, - state.env, - tokens = [] - ); - - token = state.push('footnote_ref', '', 0); - token.meta = { id: footnoteId }; - - state.env.footnotes.list[footnoteId] = { - content: state.src.slice(labelStart, labelEnd), - tokens: tokens - }; - } - - state.pos = labelEnd + 1; - state.posMax = max; - return true; - } - - // Process footnote references ([^...]) - function footnote_ref(state, silent) { - var label, - pos, - footnoteId, - footnoteSubId, - token, - max = state.posMax, - start = state.pos; - - // should be at least 4 chars - "[^x]" - if (start + 3 > max) { return false; } - - if (!state.env.footnotes || !state.env.footnotes.refs) { return false; } - if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } - if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; } - - for (pos = start + 2; pos < max; pos++) { - if (state.src.charCodeAt(pos) === 0x20) { return false; } - if (state.src.charCodeAt(pos) === 0x0A) { return false; } - if (state.src.charCodeAt(pos) === 0x5D /* ] */) { - break; - } - } - - if (pos === start + 2) { return false; } // no empty footnote labels - if (pos >= max) { return false; } - pos++; - - label = state.src.slice(start + 2, pos - 1); - if (typeof state.env.footnotes.refs[':' + label] === 'undefined') { return false; } - - if (!silent) { - if (!state.env.footnotes.list) { state.env.footnotes.list = []; } - - if (state.env.footnotes.refs[':' + label] < 0) { - footnoteId = state.env.footnotes.list.length; - state.env.footnotes.list[footnoteId] = { label: label, count: 0 }; - state.env.footnotes.refs[':' + label] = footnoteId; - } else { - footnoteId = state.env.footnotes.refs[':' + label]; - } - - footnoteSubId = state.env.footnotes.list[footnoteId].count; - state.env.footnotes.list[footnoteId].count++; - - token = state.push('footnote_ref', '', 0); - token.meta = { id: footnoteId, subId: footnoteSubId, label: label }; - } - - state.pos = pos; - state.posMax = max; - return true; - } - - // Glue footnote tokens to end of token stream - function footnote_tail(state) { - var i, l, j, t, lastParagraph, list, token, tokens, current, currentLabel, - insideRef = false, - refTokens = {}; - - if (!state.env.footnotes) { return; } - - state.tokens = state.tokens.filter(function (tok) { - if (tok.type === 'footnote_reference_open') { - insideRef = true; - current = []; - currentLabel = tok.meta.label; - return false; - } - if (tok.type === 'footnote_reference_close') { - insideRef = false; - // prepend ':' to avoid conflict with Object.prototype members - refTokens[':' + currentLabel] = current; - return false; - } - if (insideRef) { current.push(tok); } - return !insideRef; - }); - - if (!state.env.footnotes.list) { return; } - list = state.env.footnotes.list; - - token = new state.Token('footnote_block_open', '', 1); - state.tokens.push(token); - - for (i = 0, l = list.length; i < l; i++) { - token = new state.Token('footnote_open', '', 1); - token.meta = { id: i, label: list[i].label }; - state.tokens.push(token); - - if (list[i].tokens) { - tokens = []; - - token = new state.Token('paragraph_open', 'p', 1); - token.block = true; - tokens.push(token); - - token = new state.Token('inline', '', 0); - token.children = list[i].tokens; - token.content = list[i].content; - tokens.push(token); - - token = new state.Token('paragraph_close', 'p', -1); - token.block = true; - tokens.push(token); - - } else if (list[i].label) { - tokens = refTokens[':' + list[i].label]; - } - - if (tokens) state.tokens = state.tokens.concat(tokens); - if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') { - lastParagraph = state.tokens.pop(); - } else { - lastParagraph = null; - } - - t = list[i].count > 0 ? list[i].count : 1; - for (j = 0; j < t; j++) { - token = new state.Token('footnote_anchor', '', 0); - token.meta = { id: i, subId: j, label: list[i].label }; - state.tokens.push(token); - } - - if (lastParagraph) { - state.tokens.push(lastParagraph); - } - - token = new state.Token('footnote_close', '', -1); - state.tokens.push(token); - } - - token = new state.Token('footnote_block_close', '', -1); - state.tokens.push(token); - } - - md.block.ruler.before('reference', 'footnote_def', footnote_def, { alt: [ 'paragraph', 'reference' ] }); - md.inline.ruler.after('image', 'footnote_inline', footnote_inline); - md.inline.ruler.after('footnote_inline', 'footnote_ref', footnote_ref); - md.core.ruler.after('inline', 'footnote_tail', footnote_tail); -}; - -},{}]},{},[1])(1) -}); diff --git a/dist/markdown-it-footnote.min.js b/dist/markdown-it-footnote.min.js deleted file mode 100644 index 87407f9..0000000 --- a/dist/markdown-it-footnote.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).markdownitFootnote=e()}}(function(){return function(){return function e(o,t,n){function r(f,l){if(!t[f]){if(!o[f]){var i="function"==typeof require&&require;if(!l&&i)return i(f,!0);if(s)return s(f,!0);var u=new Error("Cannot find module '"+f+"'");throw u.code="MODULE_NOT_FOUND",u}var a=t[f]={exports:{}};o[f][0].call(a.exports,function(e){return r(o[f][1][e]||e)},a,a.exports,e,o,t,n)}return t[f].exports}for(var s="function"==typeof require&&require,f=0;f0&&(t+=":"+e[o].meta.subId),"["+t+"]"}function s(e,o,t,n,r){var s=r.rules.footnote_anchor_name(e,o,t,n,r),f=r.rules.footnote_caption(e,o,t,n,r),l=s;return e[o].meta.subId>0&&(l+=":"+e[o].meta.subId),''+f+""}function f(e,o,t){return(t.xhtmlOut?'
    \n':'
    \n')+'
    \n
      \n'}function l(){return"
    \n
    \n"}function i(e,o,t,n,r){var s=r.rules.footnote_anchor_name(e,o,t,n,r);return e[o].meta.subId>0&&(s+=":"+e[o].meta.subId),'
  • '}function u(){return"
  • \n"}function a(e,o,t,n,r){var s=r.rules.footnote_anchor_name(e,o,t,n,r);return e[o].meta.subId>0&&(s+=":"+e[o].meta.subId),' \u21a9\ufe0e'}o.exports=function(e){var o=e.helpers.parseLinkLabel,t=e.utils.isSpace;e.renderer.rules.footnote_ref=s,e.renderer.rules.footnote_block_open=f,e.renderer.rules.footnote_block_close=l,e.renderer.rules.footnote_open=i,e.renderer.rules.footnote_close=u,e.renderer.rules.footnote_anchor=a,e.renderer.rules.footnote_caption=r,e.renderer.rules.footnote_anchor_name=n,e.block.ruler.before("reference","footnote_def",function(e,o,n,r){var s,f,l,i,u,a,c,p,d,h,k,b=e.bMarks[o]+e.tShift[o],v=e.eMarks[o];if(b+4>v)return!1;if(91!==e.src.charCodeAt(b))return!1;if(94!==e.src.charCodeAt(b+1))return!1;for(u=b+2;u=v||58!==e.src.charCodeAt(++u))return!1;if(r)return!0;for(u++,e.env.footnotes||(e.env.footnotes={}),e.env.footnotes.refs||(e.env.footnotes.refs={}),a=e.src.slice(b+2,u-2),e.env.footnotes.refs[":"+a]=-1,(c=new e.Token("footnote_reference_open","",1)).meta={label:a},c.level=e.level++,e.tokens.push(c),s=e.bMarks[o],f=e.tShift[o],l=e.sCount[o],i=e.parentType,k=u,p=d=e.sCount[o]+u-(e.bMarks[o]+e.tShift[o]);u=l||94!==e.src.charCodeAt(i)||91!==e.src.charCodeAt(i+1)||(n=i+2,(r=o(e,i+1))<0||(t||(e.env.footnotes||(e.env.footnotes={}),e.env.footnotes.list||(e.env.footnotes.list=[]),s=e.env.footnotes.list.length,e.md.inline.parse(e.src.slice(n,r),e.md,e.env,f=[]),e.push("footnote_ref","",0).meta={id:s},e.env.footnotes.list[s]={content:e.src.slice(n,r),tokens:f}),e.pos=r+1,e.posMax=l,0)))}),e.inline.ruler.after("footnote_inline","footnote_ref",function(e,o){var t,n,r,s,f=e.posMax,l=e.pos;if(l+3>f)return!1;if(!e.env.footnotes||!e.env.footnotes.refs)return!1;if(91!==e.src.charCodeAt(l))return!1;if(94!==e.src.charCodeAt(l+1))return!1;for(n=l+2;n=f||(n++,t=e.src.slice(l+2,n-1),void 0===e.env.footnotes.refs[":"+t]||(o||(e.env.footnotes.list||(e.env.footnotes.list=[]),e.env.footnotes.refs[":"+t]<0?(r=e.env.footnotes.list.length,e.env.footnotes.list[r]={label:t,count:0},e.env.footnotes.refs[":"+t]=r):r=e.env.footnotes.refs[":"+t],s=e.env.footnotes.list[r].count,e.env.footnotes.list[r].count++,e.push("footnote_ref","",0).meta={id:r,subId:s,label:t}),e.pos=n,e.posMax=f,0)))}),e.core.ruler.after("inline","footnote_tail",function(e){var o,t,n,r,s,f,l,i,u,a,c=!1,p={};if(e.env.footnotes&&(e.tokens=e.tokens.filter(function(e){return"footnote_reference_open"===e.type?(c=!0,u=[],a=e.meta.label,!1):"footnote_reference_close"===e.type?(c=!1,p[":"+a]=u,!1):(c&&u.push(e),!c)}),e.env.footnotes.list)){for(f=e.env.footnotes.list,l=new e.Token("footnote_block_open","",1),e.tokens.push(l),o=0,t=f.length;o0?f[o].count:1,n=0;n 0) { - n += ':' + tokens[idx].meta.subId; - } - - return '[' + n + ']'; -} - -function render_footnote_ref(tokens, idx, options, env, slf) { - var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf); - var caption = slf.rules.footnote_caption(tokens, idx, options, env, slf); - var refid = id; - - if (tokens[idx].meta.subId > 0) { - refid += ':' + tokens[idx].meta.subId; - } - - return '' + caption + ''; -} - -function render_footnote_block_open(tokens, idx, options) { - return (options.xhtmlOut ? '
    \n' : '
    \n') + - '
    \n' + - '
      \n'; -} - -function render_footnote_block_close() { - return '
    \n
    \n'; -} - -function render_footnote_open(tokens, idx, options, env, slf) { - var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf); - - if (tokens[idx].meta.subId > 0) { - id += ':' + tokens[idx].meta.subId; - } - - return '
  • '; -} - -function render_footnote_close() { - return '
  • \n'; -} - -function render_footnote_anchor(tokens, idx, options, env, slf) { - var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf); - - if (tokens[idx].meta.subId > 0) { - id += ':' + tokens[idx].meta.subId; - } - - /* ↩ with escape code to prevent display as Apple Emoji on iOS */ - return ' \u21a9\uFE0E'; -} - - -module.exports = function footnote_plugin(md) { - var parseLinkLabel = md.helpers.parseLinkLabel, - isSpace = md.utils.isSpace; - - md.renderer.rules.footnote_ref = render_footnote_ref; - md.renderer.rules.footnote_block_open = render_footnote_block_open; - md.renderer.rules.footnote_block_close = render_footnote_block_close; - md.renderer.rules.footnote_open = render_footnote_open; - md.renderer.rules.footnote_close = render_footnote_close; - md.renderer.rules.footnote_anchor = render_footnote_anchor; - - // helpers (only used in other rules, no tokens are attached to those) - md.renderer.rules.footnote_caption = render_footnote_caption; - md.renderer.rules.footnote_anchor_name = render_footnote_anchor_name; - - // Process footnote block definition - function footnote_def(state, startLine, endLine, silent) { - var oldBMark, oldTShift, oldSCount, oldParentType, pos, label, token, - initial, offset, ch, posAfterColon, - start = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; - - // line should be at least 5 chars - "[^x]:" - if (start + 4 > max) { return false; } - - if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } - if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; } - - for (pos = start + 2; pos < max; pos++) { - if (state.src.charCodeAt(pos) === 0x20) { return false; } - if (state.src.charCodeAt(pos) === 0x5D /* ] */) { - break; - } - } - - if (pos === start + 2) { return false; } // no empty footnote labels - if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 0x3A /* : */) { return false; } - if (silent) { return true; } - pos++; - - if (!state.env.footnotes) { state.env.footnotes = {}; } - if (!state.env.footnotes.refs) { state.env.footnotes.refs = {}; } - label = state.src.slice(start + 2, pos - 2); - state.env.footnotes.refs[':' + label] = -1; - - token = new state.Token('footnote_reference_open', '', 1); - token.meta = { label: label }; - token.level = state.level++; - state.tokens.push(token); - - oldBMark = state.bMarks[startLine]; - oldTShift = state.tShift[startLine]; - oldSCount = state.sCount[startLine]; - oldParentType = state.parentType; - - posAfterColon = pos; - initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); - - while (pos < max) { - ch = state.src.charCodeAt(pos); - - if (isSpace(ch)) { - if (ch === 0x09) { - offset += 4 - offset % 4; - } else { - offset++; - } - } else { - break; - } - - pos++; - } - - state.tShift[startLine] = pos - posAfterColon; - state.sCount[startLine] = offset - initial; - - state.bMarks[startLine] = posAfterColon; - state.blkIndent += 4; - state.parentType = 'footnote'; - - if (state.sCount[startLine] < state.blkIndent) { - state.sCount[startLine] += state.blkIndent; - } - - state.md.block.tokenize(state, startLine, endLine, true); - - state.parentType = oldParentType; - state.blkIndent -= 4; - state.tShift[startLine] = oldTShift; - state.sCount[startLine] = oldSCount; - state.bMarks[startLine] = oldBMark; - - token = new state.Token('footnote_reference_close', '', -1); - token.level = --state.level; - state.tokens.push(token); - - return true; - } - - // Process inline footnotes (^[...]) - function footnote_inline(state, silent) { - var labelStart, - labelEnd, - footnoteId, - token, - tokens, - max = state.posMax, - start = state.pos; - - if (start + 2 >= max) { return false; } - if (state.src.charCodeAt(start) !== 0x5E/* ^ */) { return false; } - if (state.src.charCodeAt(start + 1) !== 0x5B/* [ */) { return false; } - - labelStart = start + 2; - labelEnd = parseLinkLabel(state, start + 1); - - // parser failed to find ']', so it's not a valid note - if (labelEnd < 0) { return false; } - - // We found the end of the link, and know for a fact it's a valid link; - // so all that's left to do is to call tokenizer. - // - if (!silent) { - if (!state.env.footnotes) { state.env.footnotes = {}; } - if (!state.env.footnotes.list) { state.env.footnotes.list = []; } - footnoteId = state.env.footnotes.list.length; - - state.md.inline.parse( - state.src.slice(labelStart, labelEnd), - state.md, - state.env, - tokens = [] - ); - - token = state.push('footnote_ref', '', 0); - token.meta = { id: footnoteId }; - - state.env.footnotes.list[footnoteId] = { - content: state.src.slice(labelStart, labelEnd), - tokens: tokens - }; - } - - state.pos = labelEnd + 1; - state.posMax = max; - return true; - } - - // Process footnote references ([^...]) - function footnote_ref(state, silent) { - var label, - pos, - footnoteId, - footnoteSubId, - token, - max = state.posMax, - start = state.pos; - - // should be at least 4 chars - "[^x]" - if (start + 3 > max) { return false; } - - if (!state.env.footnotes || !state.env.footnotes.refs) { return false; } - if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } - if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; } - - for (pos = start + 2; pos < max; pos++) { - if (state.src.charCodeAt(pos) === 0x20) { return false; } - if (state.src.charCodeAt(pos) === 0x0A) { return false; } - if (state.src.charCodeAt(pos) === 0x5D /* ] */) { - break; - } - } - - if (pos === start + 2) { return false; } // no empty footnote labels - if (pos >= max) { return false; } - pos++; - - label = state.src.slice(start + 2, pos - 1); - if (typeof state.env.footnotes.refs[':' + label] === 'undefined') { return false; } - - if (!silent) { - if (!state.env.footnotes.list) { state.env.footnotes.list = []; } - - if (state.env.footnotes.refs[':' + label] < 0) { - footnoteId = state.env.footnotes.list.length; - state.env.footnotes.list[footnoteId] = { label: label, count: 0 }; - state.env.footnotes.refs[':' + label] = footnoteId; - } else { - footnoteId = state.env.footnotes.refs[':' + label]; - } - - footnoteSubId = state.env.footnotes.list[footnoteId].count; - state.env.footnotes.list[footnoteId].count++; - - token = state.push('footnote_ref', '', 0); - token.meta = { id: footnoteId, subId: footnoteSubId, label: label }; - } - - state.pos = pos; - state.posMax = max; - return true; - } - - // Glue footnote tokens to end of token stream - function footnote_tail(state) { - var i, l, j, t, lastParagraph, list, token, tokens, current, currentLabel, - insideRef = false, - refTokens = {}; - - if (!state.env.footnotes) { return; } - - state.tokens = state.tokens.filter(function (tok) { - if (tok.type === 'footnote_reference_open') { - insideRef = true; - current = []; - currentLabel = tok.meta.label; - return false; - } - if (tok.type === 'footnote_reference_close') { - insideRef = false; - // prepend ':' to avoid conflict with Object.prototype members - refTokens[':' + currentLabel] = current; - return false; - } - if (insideRef) { current.push(tok); } - return !insideRef; - }); - - if (!state.env.footnotes.list) { return; } - list = state.env.footnotes.list; - - token = new state.Token('footnote_block_open', '', 1); - state.tokens.push(token); - - for (i = 0, l = list.length; i < l; i++) { - token = new state.Token('footnote_open', '', 1); - token.meta = { id: i, label: list[i].label }; - state.tokens.push(token); - - if (list[i].tokens) { - tokens = []; - - token = new state.Token('paragraph_open', 'p', 1); - token.block = true; - tokens.push(token); - - token = new state.Token('inline', '', 0); - token.children = list[i].tokens; - token.content = list[i].content; - tokens.push(token); - - token = new state.Token('paragraph_close', 'p', -1); - token.block = true; - tokens.push(token); - - } else if (list[i].label) { - tokens = refTokens[':' + list[i].label]; - } - - if (tokens) state.tokens = state.tokens.concat(tokens); - if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') { - lastParagraph = state.tokens.pop(); - } else { - lastParagraph = null; - } - - t = list[i].count > 0 ? list[i].count : 1; - for (j = 0; j < t; j++) { - token = new state.Token('footnote_anchor', '', 0); - token.meta = { id: i, subId: j, label: list[i].label }; - state.tokens.push(token); - } - - if (lastParagraph) { - state.tokens.push(lastParagraph); - } - - token = new state.Token('footnote_close', '', -1); - state.tokens.push(token); - } - - token = new state.Token('footnote_block_close', '', -1); - state.tokens.push(token); - } - - md.block.ruler.before('reference', 'footnote_def', footnote_def, { alt: [ 'paragraph', 'reference' ] }); - md.inline.ruler.after('image', 'footnote_inline', footnote_inline); - md.inline.ruler.after('footnote_inline', 'footnote_ref', footnote_ref); - md.core.ruler.after('inline', 'footnote_tail', footnote_tail); -}; diff --git a/index.mjs b/index.mjs new file mode 100644 index 0000000..48277ca --- /dev/null +++ b/index.mjs @@ -0,0 +1,353 @@ +// Process footnotes +// +'use strict' + +/// ///////////////////////////////////////////////////////////////////////////// +// Renderer partials + +function render_footnote_anchor_name (tokens, idx, options, env/*, slf */) { + const n = Number(tokens[idx].meta.id + 1).toString() + let prefix = '' + + if (typeof env.docId === 'string') prefix = `-${env.docId}-` + + return prefix + n +} + +function render_footnote_caption (tokens, idx/*, options, env, slf */) { + let n = Number(tokens[idx].meta.id + 1).toString() + + if (tokens[idx].meta.subId > 0) n += `:${tokens[idx].meta.subId}` + + return `[${n}]` +} + +function render_footnote_ref (tokens, idx, options, env, slf) { + const id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf) + const caption = slf.rules.footnote_caption(tokens, idx, options, env, slf) + let refid = id + + if (tokens[idx].meta.subId > 0) refid += `:${tokens[idx].meta.subId}` + + return `${caption}` +} + +function render_footnote_block_open (tokens, idx, options) { + return (options.xhtmlOut ? '
    \n' : '
    \n') + + '
    \n' + + '
      \n' +} + +function render_footnote_block_close () { + return '
    \n
    \n' +} + +function render_footnote_open (tokens, idx, options, env, slf) { + let id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf) + + if (tokens[idx].meta.subId > 0) id += `:${tokens[idx].meta.subId}` + + return `
  • ` +} + +function render_footnote_close () { + return '
  • \n' +} + +function render_footnote_anchor (tokens, idx, options, env, slf) { + let id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf) + + if (tokens[idx].meta.subId > 0) id += `:${tokens[idx].meta.subId}` + + /* ↩ with escape code to prevent display as Apple Emoji on iOS */ + return ` \u21a9\uFE0E` +} + +export default function footnote_plugin (md) { + const parseLinkLabel = md.helpers.parseLinkLabel + const isSpace = md.utils.isSpace + + md.renderer.rules.footnote_ref = render_footnote_ref + md.renderer.rules.footnote_block_open = render_footnote_block_open + md.renderer.rules.footnote_block_close = render_footnote_block_close + md.renderer.rules.footnote_open = render_footnote_open + md.renderer.rules.footnote_close = render_footnote_close + md.renderer.rules.footnote_anchor = render_footnote_anchor + + // helpers (only used in other rules, no tokens are attached to those) + md.renderer.rules.footnote_caption = render_footnote_caption + md.renderer.rules.footnote_anchor_name = render_footnote_anchor_name + + // Process footnote block definition + function footnote_def (state, startLine, endLine, silent) { + const start = state.bMarks[startLine] + state.tShift[startLine] + const max = state.eMarks[startLine] + + // line should be at least 5 chars - "[^x]:" + if (start + 4 > max) return false + + if (state.src.charCodeAt(start) !== 0x5B/* [ */) return false + if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) return false + + let pos + + for (pos = start + 2; pos < max; pos++) { + if (state.src.charCodeAt(pos) === 0x20) return false + if (state.src.charCodeAt(pos) === 0x5D /* ] */) { + break + } + } + + if (pos === start + 2) return false // no empty footnote labels + if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 0x3A /* : */) return false + if (silent) return true + pos++ + + if (!state.env.footnotes) state.env.footnotes = {} + if (!state.env.footnotes.refs) state.env.footnotes.refs = {} + const label = state.src.slice(start + 2, pos - 2) + state.env.footnotes.refs[`:${label}`] = -1 + + const token_fref_o = new state.Token('footnote_reference_open', '', 1) + token_fref_o.meta = { label } + token_fref_o.level = state.level++ + state.tokens.push(token_fref_o) + + const oldBMark = state.bMarks[startLine] + const oldTShift = state.tShift[startLine] + const oldSCount = state.sCount[startLine] + const oldParentType = state.parentType + + const posAfterColon = pos + const initial = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]) + let offset = initial + + while (pos < max) { + const ch = state.src.charCodeAt(pos) + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - offset % 4 + } else { + offset++ + } + } else { + break + } + + pos++ + } + + state.tShift[startLine] = pos - posAfterColon + state.sCount[startLine] = offset - initial + + state.bMarks[startLine] = posAfterColon + state.blkIndent += 4 + state.parentType = 'footnote' + + if (state.sCount[startLine] < state.blkIndent) { + state.sCount[startLine] += state.blkIndent + } + + state.md.block.tokenize(state, startLine, endLine, true) + + state.parentType = oldParentType + state.blkIndent -= 4 + state.tShift[startLine] = oldTShift + state.sCount[startLine] = oldSCount + state.bMarks[startLine] = oldBMark + + const token_fref_c = new state.Token('footnote_reference_close', '', -1) + token_fref_c.level = --state.level + state.tokens.push(token_fref_c) + + return true + } + + // Process inline footnotes (^[...]) + function footnote_inline (state, silent) { + const max = state.posMax + const start = state.pos + + if (start + 2 >= max) return false + if (state.src.charCodeAt(start) !== 0x5E/* ^ */) return false + if (state.src.charCodeAt(start + 1) !== 0x5B/* [ */) return false + + const labelStart = start + 2 + const labelEnd = parseLinkLabel(state, start + 1) + + // parser failed to find ']', so it's not a valid note + if (labelEnd < 0) return false + + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + if (!state.env.footnotes) state.env.footnotes = {} + if (!state.env.footnotes.list) state.env.footnotes.list = [] + const footnoteId = state.env.footnotes.list.length + const tokens = [] + + state.md.inline.parse( + state.src.slice(labelStart, labelEnd), + state.md, + state.env, + tokens + ) + + const token = state.push('footnote_ref', '', 0) + token.meta = { id: footnoteId } + + state.env.footnotes.list[footnoteId] = { + content: state.src.slice(labelStart, labelEnd), + tokens + } + } + + state.pos = labelEnd + 1 + state.posMax = max + return true + } + + // Process footnote references ([^...]) + function footnote_ref (state, silent) { + const max = state.posMax + const start = state.pos + + // should be at least 4 chars - "[^x]" + if (start + 3 > max) return false + + if (!state.env.footnotes || !state.env.footnotes.refs) return false + if (state.src.charCodeAt(start) !== 0x5B/* [ */) return false + if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) return false + + let pos + + for (pos = start + 2; pos < max; pos++) { + if (state.src.charCodeAt(pos) === 0x20) return false + if (state.src.charCodeAt(pos) === 0x0A) return false + if (state.src.charCodeAt(pos) === 0x5D /* ] */) { + break + } + } + + if (pos === start + 2) return false // no empty footnote labels + if (pos >= max) return false + pos++ + + const label = state.src.slice(start + 2, pos - 1) + if (typeof state.env.footnotes.refs[`:${label}`] === 'undefined') return false + + if (!silent) { + if (!state.env.footnotes.list) state.env.footnotes.list = [] + + let footnoteId + + if (state.env.footnotes.refs[`:${label}`] < 0) { + footnoteId = state.env.footnotes.list.length + state.env.footnotes.list[footnoteId] = { label, count: 0 } + state.env.footnotes.refs[`:${label}`] = footnoteId + } else { + footnoteId = state.env.footnotes.refs[`:${label}`] + } + + const footnoteSubId = state.env.footnotes.list[footnoteId].count + state.env.footnotes.list[footnoteId].count++ + + const token = state.push('footnote_ref', '', 0) + token.meta = { id: footnoteId, subId: footnoteSubId, label } + } + + state.pos = pos + state.posMax = max + return true + } + + // Glue footnote tokens to end of token stream + function footnote_tail (state) { + let tokens + let current + let currentLabel + let insideRef = false + const refTokens = {} + + if (!state.env.footnotes) { return } + + state.tokens = state.tokens.filter(function (tok) { + if (tok.type === 'footnote_reference_open') { + insideRef = true + current = [] + currentLabel = tok.meta.label + return false + } + if (tok.type === 'footnote_reference_close') { + insideRef = false + // prepend ':' to avoid conflict with Object.prototype members + refTokens[':' + currentLabel] = current + return false + } + if (insideRef) { current.push(tok) } + return !insideRef + }) + + if (!state.env.footnotes.list) { return } + const list = state.env.footnotes.list + + state.tokens.push(new state.Token('footnote_block_open', '', 1)) + + for (let i = 0, l = list.length; i < l; i++) { + const token_fo = new state.Token('footnote_open', '', 1) + token_fo.meta = { id: i, label: list[i].label } + state.tokens.push(token_fo) + + if (list[i].tokens) { + tokens = [] + + const token_po = new state.Token('paragraph_open', 'p', 1) + token_po.block = true + tokens.push(token_po) + + const token_i = new state.Token('inline', '', 0) + token_i.children = list[i].tokens + token_i.content = list[i].content + tokens.push(token_i) + + const token_pc = new state.Token('paragraph_close', 'p', -1) + token_pc.block = true + tokens.push(token_pc) + } else if (list[i].label) { + tokens = refTokens[`:${list[i].label}`] + } + + if (tokens) state.tokens = state.tokens.concat(tokens) + + let lastParagraph + + if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') { + lastParagraph = state.tokens.pop() + } else { + lastParagraph = null + } + + const t = list[i].count > 0 ? list[i].count : 1 + for (let j = 0; j < t; j++) { + const token_a = new state.Token('footnote_anchor', '', 0) + token_a.meta = { id: i, subId: j, label: list[i].label } + state.tokens.push(token_a) + } + + if (lastParagraph) { + state.tokens.push(lastParagraph) + } + + state.tokens.push(new state.Token('footnote_close', '', -1)) + } + + state.tokens.push(new state.Token('footnote_block_close', '', -1)) + } + + md.block.ruler.before('reference', 'footnote_def', footnote_def, { alt: ['paragraph', 'reference'] }) + md.inline.ruler.after('image', 'footnote_inline', footnote_inline) + md.inline.ruler.after('footnote_inline', 'footnote_ref', footnote_ref) + md.core.ruler.after('inline', 'footnote_tail', footnote_tail) +}; diff --git a/package.json b/package.json index ee292a6..f27d3dc 100644 --- a/package.json +++ b/package.json @@ -10,17 +10,39 @@ ], "repository": "markdown-it/markdown-it-footnote", "license": "MIT", + "main": "dist/index.cjs.js", + "module": "index.mjs", + "exports": { + ".": { + "require": "./dist/index.cjs.js", + "import": "./index.mjs" + }, + "./*": { + "require": "./*", + "import": "./*" + } + }, + "files": [ + "index.mjs", + "lib/", + "dist/" + ], "scripts": { - "test": "make test" + "lint": "eslint .", + "build": "rollup -c", + "test": "npm run lint && npm run build && c8 --exclude dist --exclude test -r text -r html -r lcov mocha", + "prepublishOnly": "npm run lint && npm run build" }, "devDependencies": { - "browserify": "^16.2.3", - "coveralls": "^3.0.2", - "eslint": "^5.9.0", - "istanbul": "^0.4.5", - "markdown-it": "markdown-it/markdown-it", - "markdown-it-testgen": "~0.1.0", - "mocha": "^5.2.0", - "terser": "^3.17.0" + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", + "c8": "^8.0.1", + "eslint": "^8.55.0", + "eslint-config-standard": "^17.1.0", + "markdown-it": "^13.0.2", + "markdown-it-testgen": "^0.1.6", + "mocha": "^10.2.0", + "rollup": "^4.6.1" } } diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..000ffdf --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,74 @@ +import resolve from '@rollup/plugin-node-resolve' +import terser from '@rollup/plugin-terser' +import { babel } from '@rollup/plugin-babel' +import { readFileSync } from 'fs' + +const pkg = JSON.parse(readFileSync(new URL('package.json', import.meta.url))) + +function globalName (name) { + const parts = name.split('-') + for (let i = 2; i < parts.length; i++) { + parts[i] = parts[i][0].toUpperCase() + parts[i].slice(1) + } + return parts.join('') +} + +const config_umd_full = { + input: 'index.mjs', + output: [ + { + file: `dist/${pkg.name}.js`, + format: 'umd', + name: globalName(pkg.name), + plugins: [ + // Here terser is used only to force ascii output + terser({ + mangle: false, + compress: false, + format: { comments: 'all', beautify: true, ascii_only: true, indent_level: 2 } + }) + ] + }, + { + file: `dist/${pkg.name}.min.js`, + format: 'umd', + name: globalName(pkg.name), + plugins: [ + terser({ + format: { ascii_only: true } + }) + ] + } + ], + plugins: [ + resolve(), + babel({ babelHelpers: 'bundled' }), + { + banner () { + return `/*! ${pkg.name} ${pkg.version} https://github.com/${pkg.repository} @license ${pkg.license} */` + } + } + ] +} + +const config_cjs_no_deps = { + input: 'index.mjs', + output: { + file: 'dist/index.cjs.js', + format: 'cjs' + }, + external: Object.keys(pkg.dependencies || {}), + plugins: [ + resolve(), + babel({ babelHelpers: 'bundled' }) + ] +} + +let config = [ + config_umd_full, + config_cjs_no_deps +] + +if (process.env.CJS_ONLY) config = [config_cjs_no_deps] + +export default config diff --git a/test/cjs.js b/test/cjs.js new file mode 100644 index 0000000..333a5e6 --- /dev/null +++ b/test/cjs.js @@ -0,0 +1,11 @@ +'use strict' +/* eslint-env mocha */ + +const assert = require('node:assert') +const fn = require('../') + +describe('CJS', () => { + it('require', () => { + assert.ok(typeof fn === 'function') + }) +}) diff --git a/test/test.js b/test/test.mjs similarity index 54% rename from test/test.js rename to test/test.mjs index 6a29b8f..da74c5c 100644 --- a/test/test.js +++ b/test/test.mjs @@ -1,19 +1,18 @@ -'use strict'; +import { fileURLToPath } from 'node:url' +import path from 'node:path' +import assert from 'node:assert' +import markdownit from 'markdown-it' +import testgen from 'markdown-it-testgen' - -var assert = require('assert'); -var testgen = require('markdown-it-testgen'); -var path = require('path'); - -/*eslint-env mocha*/ +import footnote from '../index.mjs' // Most of the rest of this is inlined from generate(), but modified // so we can pass in an `env` object -function generate(fixturePath, md, env) { +function generate (fixturePath, md, env) { testgen.load(fixturePath, {}, function (data) { - data.meta = data.meta || {}; + data.meta = data.meta || {} - var desc = data.meta.desc || path.relative(fixturePath, data.file); + const desc = data.meta.desc || path.relative(fixturePath, data.file); (data.meta.skip ? describe.skip : describe)(desc, function () { data.fixtures.forEach(function (fixture) { @@ -23,24 +22,23 @@ function generate(fixturePath, md, env) { assert.strictEqual( md.render(fixture.first.text, Object.assign({}, env || {})), fixture.second.text.replace(/\u21a9(?!\ufe0e)/g, '\u21a9\ufe0e') - ); - }); - }); - }); - }); + ) + }) + }) + }) + }) } - describe('footnote.txt', function () { - var md = require('markdown-it')({ linkify: true }).use(require('../')); + const md = markdownit({ linkify: true }).use(footnote) // Check that defaults work correctly - generate(path.join(__dirname, 'fixtures/footnote.txt'), md); -}); + generate(fileURLToPath(new URL('fixtures/footnote.txt', import.meta.url)), md) +}) describe('custom docId in env', function () { - var md = require('markdown-it')().use(require('../')); + const md = markdownit().use(footnote) // Now check that using `env.documentId` works to prefix IDs - generate(path.join(__dirname, 'fixtures/footnote-prefixed.txt'), md, { docId: 'test-doc-id' }); -}); + generate(fileURLToPath(new URL('fixtures/footnote-prefixed.txt', import.meta.url)), md, { docId: 'test-doc-id' }) +})