feat: support CJK & ANS spacing (#12)
This commit is contained in:
parent
898d2de3d8
commit
aebe2137b0
25 changed files with 1420 additions and 335 deletions
33
.github/workflows/deploy.yml
vendored
33
.github/workflows/deploy.yml
vendored
|
@ -9,13 +9,26 @@ jobs:
|
|||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: Cecilapp/GitHub-Pages-deploy@master
|
||||
env:
|
||||
EMAIL: sun.sivan@gmail.com
|
||||
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
BASE_BRANCH: master
|
||||
BUILD_DIR: demo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
- name: NPM Install and Test
|
||||
run: |
|
||||
npm install
|
||||
npm run test
|
||||
npm run build
|
||||
- name: Publish
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
- name: Deploy to gh-pages
|
||||
uses: JamesIves/github-pages-deploy-action@releases/v3
|
||||
with:
|
||||
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
BRANCH: gh-pages
|
||||
FOLDER: _site
|
||||
|
|
31
.github/workflows/nodejs.yml
vendored
Normal file
31
.github/workflows/nodejs.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
|
||||
name: Node CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: npm install and test
|
||||
run: |
|
||||
npm ci
|
||||
npm test
|
||||
env:
|
||||
CI: true
|
||||
- name: npm build
|
||||
run: npm run build
|
||||
env:
|
||||
CI: true
|
21
.npmignore
21
.npmignore
|
@ -1,21 +0,0 @@
|
|||
# Editor generate files
|
||||
.idea/
|
||||
.settings/
|
||||
|
||||
# Dev dependencies and cache files
|
||||
demo/
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
|
||||
# Folder view configuration files
|
||||
.DS_Store
|
||||
Desktop.ini
|
||||
|
||||
# Thumbnail cache files
|
||||
*~
|
||||
._*
|
||||
Thumbs.db
|
||||
|
||||
# Files that might appear on external disks
|
||||
.Spotlight-V100
|
||||
.Trashes
|
13
README.md
13
README.md
|
@ -5,16 +5,15 @@
|
|||
预览:[https://sivan.github.io/heti/](https://sivan.github.io/heti/)
|
||||
|
||||
主要特性:
|
||||
- 全标签样式统一;
|
||||
- 贴合网格的排版;
|
||||
- 预置简体/繁体中文多种预设字体族(仅限桌面端);
|
||||
- 预置横排、直排(竖排)样式;
|
||||
- 全标签样式美化;
|
||||
- 预置古文、诗词样式;
|
||||
- 预置行间注排版样式;
|
||||
- 预置多栏排版样式;
|
||||
- 预置多种排版样式(行间注、多栏、竖排等);
|
||||
- 多种预设字体族(仅限桌面端);
|
||||
- 简/繁体中文支持;
|
||||
- 中西文混排美化(基于JavaScript脚本实现);
|
||||
- 兼容 *normalize.css*、*CSS Reset* 等常见样式重置;
|
||||
- 移动端支持;
|
||||
- 基于 BEM;
|
||||
- ……
|
||||
|
||||
总之,用上就会变好看。
|
||||
|
@ -37,9 +36,9 @@
|
|||
|
||||
## WIP
|
||||
|
||||
- [ ] 中、西文混排
|
||||
- [ ] 标点挤压
|
||||
- [ ] 标点悬挂
|
||||
- [x] 中、西文混排
|
||||
- [x] 繁体中文支持
|
||||
- [x] 诗词版式
|
||||
- [x] 行间注版式
|
||||
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
753
_site/heti-addon.js
Normal file
753
_site/heti-addon.js
Normal file
|
@ -0,0 +1,753 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = global || self, global.Heti = factory());
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
||||
|
||||
function createCommonjsModule(fn, module) {
|
||||
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
||||
}
|
||||
|
||||
var findAndReplaceDOMText = createCommonjsModule(function (module) {
|
||||
/**
|
||||
* findAndReplaceDOMText v 0.4.6
|
||||
* @author James Padolsey http://james.padolsey.com
|
||||
* @license http://unlicense.org/UNLICENSE
|
||||
*
|
||||
* Matches the text of a DOM node against a regular expression
|
||||
* and replaces each match (or node-separated portions of the match)
|
||||
* in the specified element.
|
||||
*/
|
||||
(function (root, factory) {
|
||||
if ( module.exports) {
|
||||
// Node/CommonJS
|
||||
module.exports = factory();
|
||||
} else {
|
||||
// Browser globals
|
||||
root.findAndReplaceDOMText = factory();
|
||||
}
|
||||
}(commonjsGlobal, function factory() {
|
||||
|
||||
var PORTION_MODE_RETAIN = 'retain';
|
||||
var PORTION_MODE_FIRST = 'first';
|
||||
|
||||
var doc = document;
|
||||
var hasOwn = {}.hasOwnProperty;
|
||||
|
||||
function escapeRegExp(s) {
|
||||
return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
|
||||
}
|
||||
|
||||
function exposed() {
|
||||
// Try deprecated arg signature first:
|
||||
return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments);
|
||||
}
|
||||
|
||||
function deprecated(regex, node, replacement, captureGroup, elFilter) {
|
||||
if ((node && !node.nodeType) && arguments.length <= 2) {
|
||||
return false;
|
||||
}
|
||||
var isReplacementFunction = typeof replacement == 'function';
|
||||
|
||||
if (isReplacementFunction) {
|
||||
replacement = (function(original) {
|
||||
return function(portion, match) {
|
||||
return original(portion.text, match.startIndex);
|
||||
};
|
||||
}(replacement));
|
||||
}
|
||||
|
||||
// Awkward support for deprecated argument signature (<0.4.0)
|
||||
var instance = findAndReplaceDOMText(node, {
|
||||
|
||||
find: regex,
|
||||
|
||||
wrap: isReplacementFunction ? null : replacement,
|
||||
replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'),
|
||||
|
||||
prepMatch: function(m, mi) {
|
||||
|
||||
// Support captureGroup (a deprecated feature)
|
||||
|
||||
if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches';
|
||||
|
||||
if (captureGroup > 0) {
|
||||
var cg = m[captureGroup];
|
||||
m.index += m[0].indexOf(cg);
|
||||
m[0] = cg;
|
||||
}
|
||||
|
||||
m.endIndex = m.index + m[0].length;
|
||||
m.startIndex = m.index;
|
||||
m.index = mi;
|
||||
|
||||
return m;
|
||||
},
|
||||
filterElements: elFilter
|
||||
});
|
||||
|
||||
exposed.revert = function() {
|
||||
return instance.revert();
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* findAndReplaceDOMText
|
||||
*
|
||||
* Locates matches and replaces with replacementNode
|
||||
*
|
||||
* @param {Node} node Element or Text node to search within
|
||||
* @param {RegExp} options.find The regular expression to match
|
||||
* @param {String|Element} [options.wrap] A NodeName, or a Node to clone
|
||||
* @param {String} [options.wrapClass] A classname to append to the wrapping element
|
||||
* @param {String|Function} [options.replace='$&'] What to replace each match with
|
||||
* @param {Function} [options.filterElements] A Function to be called to check whether to
|
||||
* process an element. (returning true = process element,
|
||||
* returning false = avoid element)
|
||||
*/
|
||||
function findAndReplaceDOMText(node, options) {
|
||||
return new Finder(node, options);
|
||||
}
|
||||
|
||||
exposed.NON_PROSE_ELEMENTS = {
|
||||
br:1, hr:1,
|
||||
// Media / Source elements:
|
||||
script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
|
||||
// Input elements
|
||||
input:1, textarea:1, select:1, option:1, optgroup: 1, button:1
|
||||
};
|
||||
|
||||
exposed.NON_CONTIGUOUS_PROSE_ELEMENTS = {
|
||||
|
||||
// Elements that will not contain prose or block elements where we don't
|
||||
// want prose to be matches across element borders:
|
||||
|
||||
// Block Elements
|
||||
address:1, article:1, aside:1, blockquote:1, dd:1, div:1,
|
||||
dl:1, fieldset:1, figcaption:1, figure:1, footer:1, form:1, h1:1, h2:1, h3:1,
|
||||
h4:1, h5:1, h6:1, header:1, hgroup:1, hr:1, main:1, nav:1, noscript:1, ol:1,
|
||||
output:1, p:1, pre:1, section:1, ul:1,
|
||||
// Other misc. elements that are not part of continuous inline prose:
|
||||
br:1, li: 1, summary: 1, dt:1, details:1, rp:1, rt:1, rtc:1,
|
||||
// Media / Source elements:
|
||||
script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
|
||||
// Input elements
|
||||
input:1, textarea:1, select:1, option:1, optgroup:1, button:1,
|
||||
// Table related elements:
|
||||
table:1, tbody:1, thead:1, th:1, tr:1, td:1, caption:1, col:1, tfoot:1, colgroup:1
|
||||
|
||||
};
|
||||
|
||||
exposed.NON_INLINE_PROSE = function(el) {
|
||||
return hasOwn.call(exposed.NON_CONTIGUOUS_PROSE_ELEMENTS, el.nodeName.toLowerCase());
|
||||
};
|
||||
|
||||
// Presets accessed via `options.preset` when calling findAndReplaceDOMText():
|
||||
exposed.PRESETS = {
|
||||
prose: {
|
||||
forceContext: exposed.NON_INLINE_PROSE,
|
||||
filterElements: function(el) {
|
||||
return !hasOwn.call(exposed.NON_PROSE_ELEMENTS, el.nodeName.toLowerCase());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exposed.Finder = Finder;
|
||||
|
||||
/**
|
||||
* Finder -- encapsulates logic to find and replace.
|
||||
*/
|
||||
function Finder(node, options) {
|
||||
|
||||
var preset = options.preset && exposed.PRESETS[options.preset];
|
||||
|
||||
options.portionMode = options.portionMode || PORTION_MODE_RETAIN;
|
||||
|
||||
if (preset) {
|
||||
for (var i in preset) {
|
||||
if (hasOwn.call(preset, i) && !hasOwn.call(options, i)) {
|
||||
options[i] = preset[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.node = node;
|
||||
this.options = options;
|
||||
|
||||
// Enable match-preparation method to be passed as option:
|
||||
this.prepMatch = options.prepMatch || this.prepMatch;
|
||||
|
||||
this.reverts = [];
|
||||
|
||||
this.matches = this.search();
|
||||
|
||||
if (this.matches.length) {
|
||||
this.processMatches();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Finder.prototype = {
|
||||
|
||||
/**
|
||||
* Searches for all matches that comply with the instance's 'match' option
|
||||
*/
|
||||
search: function() {
|
||||
|
||||
var match;
|
||||
var matchIndex = 0;
|
||||
var offset = 0;
|
||||
var regex = this.options.find;
|
||||
var textAggregation = this.getAggregateText();
|
||||
var matches = [];
|
||||
var self = this;
|
||||
|
||||
regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex;
|
||||
|
||||
matchAggregation(textAggregation);
|
||||
|
||||
function matchAggregation(textAggregation) {
|
||||
for (var i = 0, l = textAggregation.length; i < l; ++i) {
|
||||
|
||||
var text = textAggregation[i];
|
||||
|
||||
if (typeof text !== 'string') {
|
||||
// Deal with nested contexts: (recursive)
|
||||
matchAggregation(text);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (regex.global) {
|
||||
while (match = regex.exec(text)) {
|
||||
matches.push(self.prepMatch(match, matchIndex++, offset));
|
||||
}
|
||||
} else {
|
||||
if (match = text.match(regex)) {
|
||||
matches.push(self.prepMatch(match, 0, offset));
|
||||
}
|
||||
}
|
||||
|
||||
offset += text.length;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares a single match with useful meta info:
|
||||
*/
|
||||
prepMatch: function(match, matchIndex, characterOffset) {
|
||||
|
||||
if (!match[0]) {
|
||||
throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
|
||||
}
|
||||
|
||||
match.endIndex = characterOffset + match.index + match[0].length;
|
||||
match.startIndex = characterOffset + match.index;
|
||||
match.index = matchIndex;
|
||||
|
||||
return match;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets aggregate text within subject node
|
||||
*/
|
||||
getAggregateText: function() {
|
||||
|
||||
var elementFilter = this.options.filterElements;
|
||||
var forceContext = this.options.forceContext;
|
||||
|
||||
return getText(this.node);
|
||||
|
||||
/**
|
||||
* Gets aggregate text of a node without resorting
|
||||
* to broken innerText/textContent
|
||||
*/
|
||||
function getText(node) {
|
||||
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return [node.data];
|
||||
}
|
||||
|
||||
if (elementFilter && !elementFilter(node)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var txt = [''];
|
||||
var i = 0;
|
||||
|
||||
if (node = node.firstChild) do {
|
||||
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
txt[i] += node.data;
|
||||
continue;
|
||||
}
|
||||
|
||||
var innerText = getText(node);
|
||||
|
||||
if (
|
||||
forceContext &&
|
||||
node.nodeType === Node.ELEMENT_NODE &&
|
||||
(forceContext === true || forceContext(node))
|
||||
) {
|
||||
txt[++i] = innerText;
|
||||
txt[++i] = '';
|
||||
} else {
|
||||
if (typeof innerText[0] === 'string') {
|
||||
// Bridge nested text-node data so that they're
|
||||
// not considered their own contexts:
|
||||
// I.e. ['some', ['thing']] -> ['something']
|
||||
txt[i] += innerText.shift();
|
||||
}
|
||||
if (innerText.length) {
|
||||
txt[++i] = innerText;
|
||||
txt[++i] = '';
|
||||
}
|
||||
}
|
||||
} while (node = node.nextSibling);
|
||||
|
||||
return txt;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Steps through the target node, looking for matches, and
|
||||
* calling replaceFn when a match is found.
|
||||
*/
|
||||
processMatches: function() {
|
||||
|
||||
var matches = this.matches;
|
||||
var node = this.node;
|
||||
var elementFilter = this.options.filterElements;
|
||||
|
||||
var startPortion,
|
||||
endPortion,
|
||||
innerPortions = [],
|
||||
curNode = node,
|
||||
match = matches.shift(),
|
||||
atIndex = 0, // i.e. nodeAtIndex
|
||||
portionIndex = 0,
|
||||
doAvoidNode,
|
||||
nodeStack = [node];
|
||||
|
||||
out: while (true) {
|
||||
|
||||
if (curNode.nodeType === Node.TEXT_NODE) {
|
||||
|
||||
if (!endPortion && curNode.length + atIndex >= match.endIndex) {
|
||||
// We've found the ending
|
||||
// (Note that, in the case of a single portion, it'll be an
|
||||
// endPortion, not a startPortion.)
|
||||
endPortion = {
|
||||
node: curNode,
|
||||
index: portionIndex++,
|
||||
text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex),
|
||||
|
||||
// If it's the first match (atIndex==0) we should just return 0
|
||||
indexInMatch: atIndex === 0 ? 0 : atIndex - match.startIndex,
|
||||
|
||||
indexInNode: match.startIndex - atIndex,
|
||||
endIndexInNode: match.endIndex - atIndex,
|
||||
isEnd: true
|
||||
};
|
||||
|
||||
} else if (startPortion) {
|
||||
// Intersecting node
|
||||
innerPortions.push({
|
||||
node: curNode,
|
||||
index: portionIndex++,
|
||||
text: curNode.data,
|
||||
indexInMatch: atIndex - match.startIndex,
|
||||
indexInNode: 0 // always zero for inner-portions
|
||||
});
|
||||
}
|
||||
|
||||
if (!startPortion && curNode.length + atIndex > match.startIndex) {
|
||||
// We've found the match start
|
||||
startPortion = {
|
||||
node: curNode,
|
||||
index: portionIndex++,
|
||||
indexInMatch: 0,
|
||||
indexInNode: match.startIndex - atIndex,
|
||||
endIndexInNode: match.endIndex - atIndex,
|
||||
text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex)
|
||||
};
|
||||
}
|
||||
|
||||
atIndex += curNode.data.length;
|
||||
|
||||
}
|
||||
|
||||
doAvoidNode = curNode.nodeType === Node.ELEMENT_NODE && elementFilter && !elementFilter(curNode);
|
||||
|
||||
if (startPortion && endPortion) {
|
||||
|
||||
curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion);
|
||||
|
||||
// processMatches has to return the node that replaced the endNode
|
||||
// and then we step back so we can continue from the end of the
|
||||
// match:
|
||||
|
||||
atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode);
|
||||
|
||||
startPortion = null;
|
||||
endPortion = null;
|
||||
innerPortions = [];
|
||||
match = matches.shift();
|
||||
portionIndex = 0;
|
||||
|
||||
if (!match) {
|
||||
break; // no more matches
|
||||
}
|
||||
|
||||
} else if (
|
||||
!doAvoidNode &&
|
||||
(curNode.firstChild || curNode.nextSibling)
|
||||
) {
|
||||
// Move down or forward:
|
||||
if (curNode.firstChild) {
|
||||
nodeStack.push(curNode);
|
||||
curNode = curNode.firstChild;
|
||||
} else {
|
||||
curNode = curNode.nextSibling;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move forward or up:
|
||||
while (true) {
|
||||
if (curNode.nextSibling) {
|
||||
curNode = curNode.nextSibling;
|
||||
break;
|
||||
}
|
||||
curNode = nodeStack.pop();
|
||||
if (curNode === node) {
|
||||
break out;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverts ... TODO
|
||||
*/
|
||||
revert: function() {
|
||||
// Reversion occurs backwards so as to avoid nodes subsequently
|
||||
// replaced during the matching phase (a forward process):
|
||||
for (var l = this.reverts.length; l--;) {
|
||||
this.reverts[l]();
|
||||
}
|
||||
this.reverts = [];
|
||||
},
|
||||
|
||||
prepareReplacementString: function(string, portion, match) {
|
||||
var portionMode = this.options.portionMode;
|
||||
if (
|
||||
portionMode === PORTION_MODE_FIRST &&
|
||||
portion.indexInMatch > 0
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
string = string.replace(/\$(\d+|&|`|')/g, function($0, t) {
|
||||
var replacement;
|
||||
switch(t) {
|
||||
case '&':
|
||||
replacement = match[0];
|
||||
break;
|
||||
case '`':
|
||||
replacement = match.input.substring(0, match.startIndex);
|
||||
break;
|
||||
case '\'':
|
||||
replacement = match.input.substring(match.endIndex);
|
||||
break;
|
||||
default:
|
||||
replacement = match[+t] || '';
|
||||
}
|
||||
return replacement;
|
||||
});
|
||||
|
||||
if (portionMode === PORTION_MODE_FIRST) {
|
||||
return string;
|
||||
}
|
||||
|
||||
if (portion.isEnd) {
|
||||
return string.substring(portion.indexInMatch);
|
||||
}
|
||||
|
||||
return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length);
|
||||
},
|
||||
|
||||
getPortionReplacementNode: function(portion, match) {
|
||||
|
||||
var replacement = this.options.replace || '$&';
|
||||
var wrapper = this.options.wrap;
|
||||
var wrapperClass = this.options.wrapClass;
|
||||
|
||||
if (wrapper && wrapper.nodeType) {
|
||||
// Wrapper has been provided as a stencil-node for us to clone:
|
||||
var clone = doc.createElement('div');
|
||||
clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper);
|
||||
wrapper = clone.firstChild;
|
||||
}
|
||||
|
||||
if (typeof replacement == 'function') {
|
||||
replacement = replacement(portion, match);
|
||||
if (replacement && replacement.nodeType) {
|
||||
return replacement;
|
||||
}
|
||||
return doc.createTextNode(String(replacement));
|
||||
}
|
||||
|
||||
var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper;
|
||||
|
||||
if (el && wrapperClass) {
|
||||
el.className = wrapperClass;
|
||||
}
|
||||
|
||||
replacement = doc.createTextNode(
|
||||
this.prepareReplacementString(
|
||||
replacement, portion, match
|
||||
)
|
||||
);
|
||||
|
||||
if (!replacement.data) {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
if (!el) {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
el.appendChild(replacement);
|
||||
|
||||
return el;
|
||||
},
|
||||
|
||||
replaceMatch: function(match, startPortion, innerPortions, endPortion) {
|
||||
|
||||
var matchStartNode = startPortion.node;
|
||||
var matchEndNode = endPortion.node;
|
||||
|
||||
var precedingTextNode;
|
||||
var followingTextNode;
|
||||
|
||||
if (matchStartNode === matchEndNode) {
|
||||
|
||||
var node = matchStartNode;
|
||||
|
||||
if (startPortion.indexInNode > 0) {
|
||||
// Add `before` text node (before the match)
|
||||
precedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode));
|
||||
node.parentNode.insertBefore(precedingTextNode, node);
|
||||
}
|
||||
|
||||
// Create the replacement node:
|
||||
var newNode = this.getPortionReplacementNode(
|
||||
endPortion,
|
||||
match
|
||||
);
|
||||
|
||||
node.parentNode.insertBefore(newNode, node);
|
||||
|
||||
if (endPortion.endIndexInNode < node.length) { // ?????
|
||||
// Add `after` text node (after the match)
|
||||
followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode));
|
||||
node.parentNode.insertBefore(followingTextNode, node);
|
||||
}
|
||||
|
||||
node.parentNode.removeChild(node);
|
||||
|
||||
this.reverts.push(function() {
|
||||
if (precedingTextNode === newNode.previousSibling) {
|
||||
precedingTextNode.parentNode.removeChild(precedingTextNode);
|
||||
}
|
||||
if (followingTextNode === newNode.nextSibling) {
|
||||
followingTextNode.parentNode.removeChild(followingTextNode);
|
||||
}
|
||||
newNode.parentNode.replaceChild(node, newNode);
|
||||
});
|
||||
|
||||
return newNode;
|
||||
|
||||
} else {
|
||||
// Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order)
|
||||
|
||||
|
||||
precedingTextNode = doc.createTextNode(
|
||||
matchStartNode.data.substring(0, startPortion.indexInNode)
|
||||
);
|
||||
|
||||
followingTextNode = doc.createTextNode(
|
||||
matchEndNode.data.substring(endPortion.endIndexInNode)
|
||||
);
|
||||
|
||||
var firstNode = this.getPortionReplacementNode(
|
||||
startPortion,
|
||||
match
|
||||
);
|
||||
|
||||
var innerNodes = [];
|
||||
|
||||
for (var i = 0, l = innerPortions.length; i < l; ++i) {
|
||||
var portion = innerPortions[i];
|
||||
var innerNode = this.getPortionReplacementNode(
|
||||
portion,
|
||||
match
|
||||
);
|
||||
portion.node.parentNode.replaceChild(innerNode, portion.node);
|
||||
this.reverts.push((function(portion, innerNode) {
|
||||
return function() {
|
||||
innerNode.parentNode.replaceChild(portion.node, innerNode);
|
||||
};
|
||||
}(portion, innerNode)));
|
||||
innerNodes.push(innerNode);
|
||||
}
|
||||
|
||||
var lastNode = this.getPortionReplacementNode(
|
||||
endPortion,
|
||||
match
|
||||
);
|
||||
|
||||
matchStartNode.parentNode.insertBefore(precedingTextNode, matchStartNode);
|
||||
matchStartNode.parentNode.insertBefore(firstNode, matchStartNode);
|
||||
matchStartNode.parentNode.removeChild(matchStartNode);
|
||||
|
||||
matchEndNode.parentNode.insertBefore(lastNode, matchEndNode);
|
||||
matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode);
|
||||
matchEndNode.parentNode.removeChild(matchEndNode);
|
||||
|
||||
this.reverts.push(function() {
|
||||
precedingTextNode.parentNode.removeChild(precedingTextNode);
|
||||
firstNode.parentNode.replaceChild(matchStartNode, firstNode);
|
||||
followingTextNode.parentNode.removeChild(followingTextNode);
|
||||
lastNode.parentNode.replaceChild(matchEndNode, lastNode);
|
||||
});
|
||||
|
||||
return lastNode;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return exposed;
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Heti add-on v 0.1.0
|
||||
* Add right spacing between CJK & ANS characters
|
||||
*/
|
||||
|
||||
// 正则表达式来自 pangu.js https://github.com/vinta/pangu.js
|
||||
const CJK = '\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30fa\u30fc-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff';
|
||||
const A = 'A-Za-z\u0370-\u03ff';
|
||||
const N = '0-9';
|
||||
const S = '`~!@#\\$%\\^&\\*\\(\\)-_=\\+\\[\\]{}\\\\\\|;:\'",<.>\\/\\?';
|
||||
const ANS = `${A}${N}${S}`;
|
||||
const HETI_NON_CONTIGUOUS_ELEMENTS = {
|
||||
// Block Elements
|
||||
address: 1, article: 1, aside: 1, blockquote: 1, dd: 1, div: 1,
|
||||
dl: 1, fieldset: 1, figcaption: 1, figure: 1, footer: 1, form: 1, h1: 1, h2: 1, h3: 1,
|
||||
h4: 1, h5: 1, h6: 1, header: 1, hgroup: 1, hr: 1, main: 1, nav: 1, noscript: 1, ol: 1,
|
||||
output: 1, p: 1, pre: 1, section: 1, ul: 1,
|
||||
// Other misc. elements that are not part of continuous inline prose:
|
||||
br: 1, li: 1, summary: 1, dt: 1, details: 1, rp: 1, rt: 1, rtc: 1,
|
||||
// Media / Source elements:
|
||||
script: 1, style: 1, img: 1, video: 1, audio: 1, canvas: 1, svg: 1, map: 1, object: 1,
|
||||
// Input elements
|
||||
input: 1, textarea: 1, select: 1, option: 1, optgroup: 1, button: 1,
|
||||
// Table related elements:
|
||||
table: 1, tbody: 1, thead: 1, th: 1, tr: 1, td: 1, caption: 1, col: 1, tfoot: 1, colgroup: 1,
|
||||
// Inline elements
|
||||
ins: 1, del: 1, s: 1,
|
||||
};
|
||||
const HETI_SKIPPED_ELEMENTS = {
|
||||
br: 1, hr: 1,
|
||||
// Media / Source elements:
|
||||
script: 1, style: 1, img: 1, video: 1, audio: 1, canvas: 1, svg: 1, map: 1, object: 1,
|
||||
// Input elements:
|
||||
input: 1, textarea: 1, select: 1, option: 1, optgroup: 1, button: 1,
|
||||
// Pre elements:
|
||||
pre: 1, code: 1, sup: 1, sub: 1,
|
||||
// Heti elements
|
||||
'heti-spacing': 1,
|
||||
};
|
||||
const HETI_SKIPPED_CLASS = 'heti-skip';
|
||||
const hasOwn = {}.hasOwnProperty;
|
||||
|
||||
class Heti {
|
||||
constructor (rootSelector) {
|
||||
this.rootSelector = rootSelector || '.heti';
|
||||
this.REG_FULL = new RegExp(`(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`, 'g');
|
||||
this.REG_START = new RegExp(`([${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`, 'g');
|
||||
this.REG_END = new RegExp(`(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)`, 'g');
|
||||
this.funcForceContext = function forceContext (el) {
|
||||
return hasOwn.call(HETI_NON_CONTIGUOUS_ELEMENTS, el.nodeName.toLowerCase())
|
||||
};
|
||||
this.funcFilterElements = function filterElements (el) {
|
||||
return (
|
||||
!(el.classList && el.classList.contains(HETI_SKIPPED_CLASS)) &&
|
||||
!hasOwn.call(HETI_SKIPPED_ELEMENTS, el.nodeName.toLowerCase())
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
spacingElements (elmList) {
|
||||
for (let $$root of elmList) {
|
||||
this.spacingElement($$root);
|
||||
}
|
||||
}
|
||||
|
||||
spacingElement ($$elm) {
|
||||
const commonConfig = {
|
||||
forceContext: this.funcForceContext,
|
||||
filterElements: this.funcFilterElements,
|
||||
};
|
||||
const getWrapper = function (classList, text) {
|
||||
const $$r = document.createElement('heti-spacing');
|
||||
$$r.className = classList;
|
||||
$$r.textContent = text.trim();
|
||||
return $$r
|
||||
};
|
||||
|
||||
findAndReplaceDOMText($$elm, Object.assign(commonConfig, {
|
||||
find: this.REG_FULL,
|
||||
replace: portion => getWrapper('heti-spacing-start heti-spacing-end', portion.text),
|
||||
}));
|
||||
|
||||
findAndReplaceDOMText($$elm, Object.assign(commonConfig, {
|
||||
find: this.REG_START,
|
||||
replace: portion => getWrapper('heti-spacing-start', portion.text),
|
||||
}));
|
||||
|
||||
findAndReplaceDOMText($$elm, Object.assign(commonConfig, {
|
||||
find: this.REG_END,
|
||||
replace: portion => getWrapper('heti-spacing-end', portion.text),
|
||||
}));
|
||||
}
|
||||
|
||||
autoSpacing () {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $$rootList = document.querySelectorAll(this.rootSelector);
|
||||
|
||||
for (let $$root of $$rootList) {
|
||||
this.spacingElement($$root);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Heti;
|
||||
|
||||
})));
|
|
@ -71,6 +71,7 @@
|
|||
max-width: 42em;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
@ -182,19 +183,16 @@
|
|||
margin-block-end: 24px;
|
||||
font-size: 32px;
|
||||
line-height: 48px;
|
||||
letter-spacing: 1.6px;
|
||||
}
|
||||
|
||||
.heti h2 {
|
||||
font-size: 24px;
|
||||
line-height: 36px;
|
||||
letter-spacing: 1.2px;
|
||||
}
|
||||
|
||||
.heti h3 {
|
||||
font-size: 20px;
|
||||
line-height: 36px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.heti h4 {
|
||||
|
@ -212,6 +210,20 @@
|
|||
line-height: 24px;
|
||||
}
|
||||
|
||||
.heti h1,
|
||||
.heti h2,
|
||||
.heti h3 {
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.heti h1:not(:lang(zh)):not(:lang(ja)):not(:lang(kr)), .heti h1:not(:lang(zh)),
|
||||
.heti h2:not(:lang(zh)):not(:lang(ja)):not(:lang(kr)),
|
||||
.heti h2:not(:lang(zh)),
|
||||
.heti h3:not(:lang(zh)):not(:lang(ja)):not(:lang(kr)),
|
||||
.heti h3:not(:lang(zh)) {
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.heti h1 + h2,
|
||||
.heti h2 + h3,
|
||||
.heti h3 + h4,
|
||||
|
@ -373,7 +385,7 @@
|
|||
|
||||
.heti rt {
|
||||
font-size: 0.875em;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.heti small {
|
||||
|
@ -391,6 +403,7 @@
|
|||
margin-inline-end: 0.25em;
|
||||
font-size: 0.75em;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "Heti Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-style: normal;
|
||||
line-height: 1;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
@ -403,6 +416,11 @@
|
|||
top: -0.5em;
|
||||
}
|
||||
|
||||
.heti sup:target,
|
||||
.heti sup a:target {
|
||||
background-color: #def;
|
||||
}
|
||||
|
||||
.heti summary {
|
||||
padding-inline-start: 1em;
|
||||
outline: 0;
|
||||
|
@ -685,6 +703,7 @@
|
|||
.heti .heti-x-large {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.heti .heti-small {
|
||||
|
@ -734,6 +753,10 @@
|
|||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.heti .heti-fn li:target {
|
||||
background-color: #def;
|
||||
}
|
||||
|
||||
.heti .heti-hang {
|
||||
position: absolute;
|
||||
line-height: inherit;
|
||||
|
@ -748,3 +771,31 @@
|
|||
.heti .heti-em:not(:lang(zh)):not(:lang(ja)):not(:lang(kr)), .heti .heti-em:not(:lang(zh)) {
|
||||
-webkit-text-emphasis: none;
|
||||
}
|
||||
|
||||
.heti .heti-ruby--inline {
|
||||
display: inline-flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.heti .heti-ruby--inline rt {
|
||||
display: inline;
|
||||
line-height: 0.5;
|
||||
}
|
||||
|
||||
.heti heti-spacing {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.heti heti-spacing + sup,
|
||||
.heti heti-spacing + sub {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.heti .heti-spacing-start {
|
||||
margin-inline-end: 0.25em;
|
||||
}
|
||||
|
||||
.heti .heti-spacing-end {
|
||||
margin-inline-start: 0.25em;
|
||||
}
|
|
@ -50,8 +50,9 @@ a {
|
|||
|
||||
.article[data-bg-grid="grid-24"] {
|
||||
background-size: 100% 24px;
|
||||
background-image: linear-gradient(to right, rgba(255, 255, 255, 0) 31px, #eee 1px, rgba(255, 255, 255, 0) 33px),
|
||||
linear-gradient(rgba(255, 255, 255, 0) 23px, #eee 1px);
|
||||
background-image:
|
||||
linear-gradient(to right, rgba(255, 255, 255, 0) 31px, #eee 1px, rgba(255, 255, 255, 0) 33px),
|
||||
linear-gradient(rgba(255, 255, 255, 0) 23px, #eee 1px);
|
||||
outline-color: #eee;
|
||||
}
|
||||
|
||||
|
@ -63,8 +64,9 @@ a {
|
|||
|
||||
.article[data-bg-grid="grid-12"] {
|
||||
background-size: 100% 12px;
|
||||
background-image: linear-gradient(to right, rgba(255, 255, 255, 0) 31px, #eee 1px, rgba(255, 255, 255, 0) 33px),
|
||||
linear-gradient(rgba(255, 255, 255, 0) 11px, #eee 12px);
|
||||
background-image:
|
||||
linear-gradient(to right, rgba(255, 255, 255, 0) 31px, #eee 1px, rgba(255, 255, 255, 0) 33px),
|
||||
linear-gradient(rgba(255, 255, 255, 0) 11px, #eee 12px);
|
||||
outline-color: #eee;
|
||||
}
|
||||
|
||||
|
@ -313,4 +315,7 @@ a {
|
|||
margin-block-start: 12px;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
.article .article__toc ol ol {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="./normalize.css">
|
||||
<link rel="stylesheet" href="./index.css">
|
||||
<link rel="stylesheet" href="./heti.min.css">
|
||||
<link rel="stylesheet" href="./heti.css">
|
||||
<link rel="icon" href="./favicon.svg">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -15,7 +15,7 @@
|
|||
<h1>赫蹏</h1>
|
||||
<blockquote>古代称用以书写的小幅绢帛。后亦以借指纸。《汉书·外戚传下·孝成赵皇后》:<q>武(籍武 )发篋中,有裹药二枚,赫蹏书。</q><u>颜师古</u>注:<q><u>邓展</u>曰:<q>赫音兄弟鬩墙之鬩。</q><u>应劭</u>曰:<q>赫蹏,薄小纸也。</q></q><u>宋</u><u>赵彦卫</u> 《云麓漫钞》卷七:<q>《赵后传》所谓『赫蹏』者,注云<q>薄小纸</q>,然其寔亦縑帛。</q></blockquote>
|
||||
|
||||
<nav class="article__toc">
|
||||
<nav class="article__toc heti-skip">
|
||||
<details open>
|
||||
<summary>目录</summary>
|
||||
<ol>
|
||||
|
@ -31,34 +31,33 @@
|
|||
</ul>
|
||||
</li>
|
||||
<li><a href="#columns">多栏排版</a></li>
|
||||
<li><a href="#vertical">垂直排版</a></li>
|
||||
<li><a href="#guidelines">排版原则</a></li>
|
||||
<li><a href="#vertical">竖排排版</a></li>
|
||||
<li><a href="#guidelines">设计原则</a></li>
|
||||
<li>
|
||||
<a href="#appendix">附录</a>
|
||||
<ol class="heti-list-latin">
|
||||
<li><a href="#compatibility">兼容性</a></li>
|
||||
<li><a href="#tags">标签示例表</a></li>
|
||||
<li><a href="#javascript">增强脚本</a></li>
|
||||
<li><a href="#license">开源协议</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><a href="#wip">待开发功能</a></li>
|
||||
</ol>
|
||||
</details>
|
||||
</nav>
|
||||
|
||||
<h2 id="intro">介绍<a class="anchor" href="#intro">#</a></h2>
|
||||
<p><dfn>赫蹏</dfn>(<code>hètí</code>)是专为中文内容展示设计的排版样式增强。它基于通行的中文排版规范而来,可以为网站的读者带来更好的文章阅读体验。由于它不同于传统的网页样式重置,而是专注于<em>正文区域</em>的样式增强,因此可以很好的与常见的CSS样式重置方案共存。</p>
|
||||
<p><ruby class="heti-ruby--inline"><rb>赫</rb><rp>(</rp><rt>hè</rt><rp>)</rp></ruby><ruby class="heti-ruby--inline"><rb>蹏</rb><rp>(</rp><rt>tí</rt><rp>)</rp></ruby>是专为中文网页内容设计的排版样式增强。它基于通行的中文排版规范,可为网站的读者带来更好的内容阅读体验。它的主要特性有:</p>
|
||||
<ul>
|
||||
<li>全标签样式统一;</li>
|
||||
<li>贴合网格的排版;</li>
|
||||
<li>预置多种预设字体族(仅限桌面端);</li>
|
||||
<li>预置横排、直排(竖排)样式;</li>
|
||||
<li>全标签样式美化;</li>
|
||||
<li>预置古文、诗词样式;</li>
|
||||
<li>预置行间注排版样式;</li>
|
||||
<li>预置多栏排版样式;</li>
|
||||
<li>兼容<i>normalize.css</i>、<i>CSS Reset</i><sup><a href="#fn-01">[1]</a></sup>;</li>
|
||||
<li>预置多种排版样式(行间注、多栏、竖排等);</li>
|
||||
<li>多种预设字体族(仅限桌面端);</li>
|
||||
<li>简/繁体中文支持;</li>
|
||||
<li>中西文混排美化(基于JavaScript脚本实现);</li>
|
||||
<li>兼容<i>normalize.css</i>、<i>CSS Reset<sup><a id="ref-01" href="#fn-01">[1]</a></sup></i>等常见样式重置;</li>
|
||||
<li>移动端支持;</li>
|
||||
<li>基于BEM<sup><a href="#fn-02">[2]</a></sup>命名;</li>
|
||||
<li>……</li>
|
||||
</ul>
|
||||
<p>总之,用上就会变好看。</p>
|
||||
|
@ -66,31 +65,31 @@
|
|||
<hr>
|
||||
|
||||
<h2 id="usage">使用方法<a class="anchor" href="#usage">#</a></h2>
|
||||
<p>赫蹏的使用方法很简单,只需要引入样式文件并设定作用范围即可:</p>
|
||||
<p>赫蹏的源文件可在项目仓库(<a href="https://github.com/sivan/heti">https://github.com/sivan/heti</a>)下载,生产文件位于<code>dist</code>文件夹。使用方法如下:</p>
|
||||
<ol>
|
||||
<li>
|
||||
在页面的<code><head></code>标签中引入<code>heti.css</code>文件:
|
||||
在页面的<code></head></code>标签前中引入<code>heti.css</code>样式文件:
|
||||
<pre><code><link rel="stylesheet" href="./heti.css"></code></pre>
|
||||
</li>
|
||||
<li>
|
||||
在要作用的容器元素上增加<code>class="heti"</code>的类名即可:
|
||||
<pre><code><article class="entry heti">
|
||||
<pre><code><article class="entry <ins>heti</ins>">
|
||||
<h1>我的世界观</h1>
|
||||
<p>有钱人的生活就是这么朴实无华,且枯燥</p>
|
||||
……
|
||||
</article></code></pre>
|
||||
</li>
|
||||
<li>不建议将样式作用在根标签(比如<code><body></code>或<code><div class="container"></code>)上,除非你的页面<u title="就像本页面一样">通篇都是文章的一部分</u>。</li>
|
||||
<li>注意:赫蹏是正文区域的样式增强,不是<i>normalize.css</i>或<i>CSS Reset</i>的替代。因此<b>不建议</b>将它作用在根标签(如<code><body></code>或<code><div class="container"></code>)上,除非像本页面一样通篇都是文章的一部分。</li>
|
||||
</ol>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="examples">效果示例<a class="anchor" href="#examples">#</a></h2>
|
||||
<p>本站已<em>全页</em>应用了赫蹏样式,下面是赫蹏在特殊排版下的效果演示。</p>
|
||||
<p>本页面<em>全页</em>应用了赫蹏样式,所见即所得。下面是内置的多种排版效果演示。</p>
|
||||
<details open>
|
||||
<summary id="example-1">示例1:古文</summary>
|
||||
<section class="demo">
|
||||
<div class="heti heti--ancient">
|
||||
<div class="heti--ancient">
|
||||
<h1>出师表</h1>
|
||||
<p class="heti-meta heti-small">作者:<abbr title="字孔明">諸葛亮</abbr>(181年~234年10月8日)</p>
|
||||
<p>先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。</p>
|
||||
|
@ -109,7 +108,7 @@
|
|||
<details open>
|
||||
<summary id="example-2">示例2:诗词</summary>
|
||||
<section class="demo">
|
||||
<div class="heti heti--ancient">
|
||||
<div class="heti--ancient">
|
||||
<h2>一剪梅·红藕香残玉簟秋<span class="heti-meta heti-small">[宋]<abbr title="号易安居士">李清照</abbr></span></h2>
|
||||
<p class="heti-verse heti-x-large">
|
||||
红藕香残玉簟秋。轻解罗裳,独上兰舟<span class="heti-hang">。</span><br>
|
||||
|
@ -119,13 +118,13 @@
|
|||
</p>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="heti heti--poetry">
|
||||
<h2>丑奴儿·书博山道中壁<span class="heti-meta heti-small">[宋]<abbr title="号稼轩">辛弃疾</abbr></span></h2>
|
||||
<div class="heti--poetry">
|
||||
<h2>赠汪伦<span class="heti-meta heti-small">[唐]<abbr title="号青莲居士">李白</abbr></span></h2>
|
||||
<p class="heti-x-large">
|
||||
少年不识愁滋味,爱上层楼<span class="heti-hang">。</span><br>
|
||||
爱上层楼,为赋新词强说愁<span class="heti-hang">。</span><br>
|
||||
而今识尽愁滋味,欲说还休<span class="heti-hang">。</span><br>
|
||||
欲说还休,却道天凉好个秋<span class="heti-hang">。</span>
|
||||
李白乘舟将欲行<span class="heti-hang">,</span><br>
|
||||
忽闻岸上踏歌声<span class="heti-hang">。</span><br>
|
||||
桃花潭水深千尺<span class="heti-hang">,</span><br>
|
||||
不及汪伦送我情<span class="heti-hang">。</span>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -134,7 +133,7 @@
|
|||
<details open>
|
||||
<summary id="example-3">示例3:行间注</summary>
|
||||
<section class="demo">
|
||||
<div class="heti heti--ancient heti--annotation">
|
||||
<div class="heti--ancient heti--annotation">
|
||||
<h2>庖丁解牛</h2>
|
||||
<p class="heti-meta heti-small">作者:<abbr title="庄子">庄周</abbr>(公元前369~公元前286年)</p>
|
||||
<p>吾生也有涯,而知也无涯。以有涯随无涯,殆已!已而为知者,殆而已矣!为善无近名,为恶无近刑。缘督以为经,可以保身,可以全生,可以养亲,可以尽年。</p>
|
||||
|
@ -149,7 +148,7 @@
|
|||
<details>
|
||||
<summary id="example-4">示例4:英文演示</summary>
|
||||
<section class="demo">
|
||||
<div class="heti" lang="en-US">
|
||||
<div lang="en-US">
|
||||
<h1>Lorem Ipsum</h1>
|
||||
<p><q>There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain...</q></p>
|
||||
<p><dfn>Lorem Ipsum</dfn> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like <em>Aldus PageMaker</em> including versions of Lorem Ipsum.</p>
|
||||
|
@ -162,27 +161,27 @@
|
|||
<hr>
|
||||
|
||||
<h2 id="columns">多栏排版<a class="anchor" href="#columns">#</a></h2>
|
||||
<p>赫蹏预置了多种多栏布局类,可以按栏数或栏宽进行设置。</p>
|
||||
<p>赫蹏预置了多种多栏布局类,可以按栏数或每栏行宽进行设置。</p>
|
||||
<details>
|
||||
<summary>查看使用说明</summary>
|
||||
<p>为容器元素添加名为<code>heti--columns-2</code>class即可实现三栏排版。</p>
|
||||
<pre><code><div class="heti heti--columns-2"></div></code></pre>
|
||||
<p>为容器元素添加名为<code>heti--columns-2</code>的class即可实现双栏排版。</p>
|
||||
<pre><code><div class="heti heti--columns-2">...</div></code></pre>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 60px;">方式</th>
|
||||
<th style="width: 80px;">方式</th>
|
||||
<th style="width: 160px;">对应类名</th>
|
||||
<th style="width: 280px;">可选数值</th>
|
||||
<th style="width: 300px;">可选数值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>按数量</td>
|
||||
<td>按栏目数量</td>
|
||||
<td><code>heti--columns-3</code></td>
|
||||
<td>2, 3, 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>按宽度</td>
|
||||
<td>按每栏行宽</td>
|
||||
<td><code>heti--columns-16em</code></td>
|
||||
<td>16em, 20em, 24em, … +4em, … , 48em</td>
|
||||
</tr>
|
||||
|
@ -190,7 +189,7 @@
|
|||
</table>
|
||||
</details>
|
||||
<figure class="card card--multi-column">
|
||||
<section class="heti heti--columns-2">
|
||||
<section class="heti--columns-2">
|
||||
<p>先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。</p>
|
||||
<p>宫中府中,俱为一体;陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理;不宜偏私,使内外异法也。</p>
|
||||
<p>侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下:愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。</p>
|
||||
|
@ -206,16 +205,16 @@
|
|||
|
||||
<hr>
|
||||
|
||||
<h2 id="vertical">垂直排版<a class="anchor" href="#vertical">#</a></h2>
|
||||
<p>赫蹏预置了传统的垂直方向排版,同样贴合栅格。</p>
|
||||
<h2 id="vertical">竖排排版<a class="anchor" href="#vertical">#</a></h2>
|
||||
<p>赫蹏预置了传统的竖排(直排)方向排版,同样贴合栅格。</p>
|
||||
<details>
|
||||
<summary>查看使用说明</summary>
|
||||
<p>为容器元素添加名为<code>heti--vertical</code>class即可实现垂直布局:</p>
|
||||
<pre><code><div class="heti heti--vertical"></div></code></pre>
|
||||
<p>为容器元素添加名为<code>heti--vertical</code>的class即可实现竖排布局。</p>
|
||||
<pre><code><div class="heti heti--vertical">...</div></code></pre>
|
||||
</details>
|
||||
<figure class="card card--vertical">
|
||||
<div class="card__vertical-container">
|
||||
<section class="heti heti--vertical heti--ancient">
|
||||
<section class="heti--vertical heti--ancient">
|
||||
<h1>出師表</h1>
|
||||
<p class="heti-small">作者:<abbr title="字孔明">諸葛亮</abbr>(181年-234年10月8日)</p>
|
||||
<p>先帝創業未半,而中道崩殂;今天下三分,益州疲弊,此誠危急存亡之秋也﹗然侍衞之臣,不懈於內;忠志之士,忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。</p>
|
||||
|
@ -227,21 +226,20 @@
|
|||
<p>願陛下託臣以討賊興復之效;不效,則治臣之罪,以告先帝之靈。若無興德之言,則責攸之、禕、允等之慢,以彰其咎。陛下亦宜自謀,以諮諏善道,察納雅言,深追先帝遺詔。臣不勝受恩感激。今當遠離,臨表涕零,不知所言!</p>
|
||||
</section>
|
||||
</div>
|
||||
<figcaption>垂直排版演示</figcaption>
|
||||
<figcaption>竖排排版演示</figcaption>
|
||||
</figure>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="guidelines">排版原则<a class="anchor" href="#guidelines">#</a></h2>
|
||||
<h2 id="guidelines">设计原则<a class="anchor" href="#guidelines">#</a></h2>
|
||||
<p>赫蹏项目的初衷很简单:它不作为一个CSS Reset出现,而是根据通行的中文排版规范,对网页正文区域进行排版样式增强。在部分CSS特性尚未有浏览器支持前,可通过JavaScript实现部分补充(非必要)。</p>
|
||||
<h3>核心</h3>
|
||||
<p>以美化中文方块字排版为目标,假定作用范围是网页中的文章区域(含标题、正文、元信息等),不包含网页导航、表单、侧边栏等区域(它们也不需要这套格式排版)。</p>
|
||||
<p>使用<code>heti.css</code>时建议搭配良好的中文书写习惯,即:使用正确的HTML标签、正确的引号、中文全角标点等。使用Markdown书写文章将助你一臂之力。</p>
|
||||
<h3>字号</h3>
|
||||
<p><code>heti.css</code> 采用16px作为默认字号。在文字较大时(作用于标题等情况),会适当地增加字间距以便获得更好地可读性。</p>
|
||||
<h3>字体</h3>
|
||||
<p>参考《中文排版需求<sup><a href="#fn-03">[3]</a></sup>》中描述的四种常见书籍排版字体:宋体、楷体、黑体、仿宋。赫蹏提供黑体、宋体和<u title="以正文宋体、标题楷体构成的搭配">传统</u>三种字体风格,前两者分别对应无衬线、衬线字体族。</p>
|
||||
<p>美化中文方块字排版,作用范围是网页中的文章区域(含标题、正文、元信息等),不包含网页导航、表单、侧边栏等区域(它们也不需要这套格式排版)。</p>
|
||||
<p>使用<code>heti.css</code>时建议搭配良好的中文书写习惯,即:使用正确的HTML标签(可参考后面的<a href="#tags">标签示例表</a>)、正确的引号、中文全角标点等。使用Markdown书写文章将助你一臂之力。</p>
|
||||
<h3>文字</h3>
|
||||
<p>参考《中文排版需求<sup><a id="ref-02" href="#fn-02">[2]</a></sup>》中描述的常用书籍排版字体,赫蹏提供了黑体、宋体和<u title="以正文宋体、标题楷体构成的搭配">传统</u>三种字体风格,前两者分别对应无衬线、衬线字体族。文字默认采用16px作为标准字号。在标题等文字较大的情况下,会适当地增加字间距以便获得更好地可读性。</p>
|
||||
<details>
|
||||
<summary>查看对照表</summary>
|
||||
<summary>查看字体风格对照表</summary>
|
||||
<section class="section">
|
||||
<table>
|
||||
<caption>各字体族下不同标签对应的字体</caption>
|
||||
|
@ -258,7 +256,7 @@
|
|||
<tr>
|
||||
<th>标题</th>
|
||||
<td>黑体</td>
|
||||
<td>宋体(粗)</td>
|
||||
<td>宋体</td>
|
||||
<td>楷体</td>
|
||||
<td>
|
||||
<section>
|
||||
|
@ -363,7 +361,7 @@
|
|||
<h3>标点</h3>
|
||||
<p>参考《中文排版需求》制定符号样式。唯一差异在于简体中文一律采用直角引号(「」)替代圆角引号(“”),这样可以保持字符等宽。</p>
|
||||
<h3>间距</h3>
|
||||
<p>为保持页面元素总是贴合垂直栅格,大部分块级元素(段落、列表、表格等)采用一行行高作为底边距,半行行高作为顶边距。</p>
|
||||
<p>为保持页面元素总是贴合垂直栅格,块级元素(段落、列表、表格等)采用一行行高作为底边距,半行行高作为顶边距。标题根据亲密性原则采用相反的边距设计。</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
@ -402,14 +400,14 @@
|
|||
<table>
|
||||
<caption>常用标签样式示例表</caption>
|
||||
<tr>
|
||||
<th style="width: 80px;">类型</th>
|
||||
<th style="width: 72px;">类型</th>
|
||||
<th style="width: 320px;">标签</th>
|
||||
<th style="width: 240px;">效果</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>链接</td>
|
||||
<td><code><a href="https://github.com/sivan/heti" title="赫蹏" target="_blank">heti.css</a></code></td>
|
||||
<td><a href="https://github.com/sivan/heti" title="赫蹏" target="_blank">heti.css</a></td>
|
||||
<td><code><a href="https://github.com/sivan/heti" title="赫蹏">heti.css</a></code></td>
|
||||
<td><a href="https://github.com/sivan/heti" title="赫蹏">heti.css</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>缩写</td>
|
||||
|
@ -428,12 +426,12 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>文本变动</td>
|
||||
<td><code>这次考试,我考了<del datetime="17:00:00">97</del><ins datetime="18:15:00">100</ins>分呢!</code></td>
|
||||
<td>这次考试,我考了<del datetime="17:00:00">97</del><ins datetime="18:15:00">100</ins>分呢!</td>
|
||||
<td><code>这次考试,我考了<del datetime="17:00:00">58</del><ins datetime="18:15:00">98</ins>分呢!</code></td>
|
||||
<td>这次考试,我考了<del datetime="17:00:00">58</del><ins datetime="18:15:00">98</ins>分呢!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>文本更新</td>
|
||||
<td><code>因为谁也不认识,所以最后我们决定念<s>dí</s> tí。</code></td>
|
||||
<td><code>因为谁也不认识,所以最后我们决定念<s>dí</s>tí。</code></td>
|
||||
<td>因为谁也不认识,所以最后我们决定念<s>dí</s>tí。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -446,6 +444,11 @@
|
|||
<td><code><dfn>窃·格瓦拉</dfn>,中国大陆网络红人、罪犯。被奉为百度「戒赌吧」400万会员的「精神领袖」。</code></td>
|
||||
<td><dfn>窃·格瓦拉</dfn>,中国大陆网络红人、罪犯。被奉为百度「戒赌吧」400万会员的「精神领袖」。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>标记</td>
|
||||
<td><code>这道题<mark>必考</mark>,你们爱记不记。</code></td>
|
||||
<td>这道题<mark>必考</mark>,你们爱记不记。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>强调</td>
|
||||
<td><code>稳住,<em>我们能赢</em>!</code></td>
|
||||
|
@ -456,15 +459,22 @@
|
|||
<td><code>我们<span class="heti-em">必将</span>战胜这场疫情。</code></td>
|
||||
<td>我们<span class="heti-em">必将</span>战胜这场疫情。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>标记</td>
|
||||
<td><code>这道题<mark>必考</mark>,你们爱记不记。</code></td>
|
||||
<td>这道题<mark>必考</mark>,你们爱记不记。</td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
</details>
|
||||
|
||||
<h3 id="javascript">增强脚本<sup>beta</sup><a class="anchor" href="#javascript">#</a></h3>
|
||||
<p>由于部分CSS特性尚未有浏览器,所以可选择使用增强脚本进行中西文混排优化。无论你的输入习惯是否在中西文之间加入「空格」,都会统一成一样的间距(1/4字宽的空白)。</p>
|
||||
<details>
|
||||
<summary>查看使用说明</summary>
|
||||
<p>在页面的<code></body></code>标签前引入JavaScript脚本:</p>
|
||||
<pre><code><script src="./heti-addon.min.js"></script>
|
||||
<script>
|
||||
const heti = new Heti();
|
||||
heti.autoSpacing();
|
||||
</script></code></pre>
|
||||
</details>
|
||||
|
||||
<h3 id="license">开源协议<a class="anchor" href="#tags">#</a></h3>
|
||||
<p>「赫蹏」遵循MIT协议开源。</p>
|
||||
|
||||
|
@ -472,18 +482,23 @@
|
|||
|
||||
<h2 id="wip">待开发功能<a class="anchor" href="#wip">#</a></h2>
|
||||
<ul>
|
||||
<li>️☑ 中、西文混排</li>
|
||||
<li>☑ 标点挤压</li>
|
||||
<li>☑ 标点悬挂</li>
|
||||
<li>✅ 中、西文混排</li>
|
||||
<li>✅ 诗词版式</li>
|
||||
<li>✅ 行间注版式</li>
|
||||
</ul>
|
||||
|
||||
<footer class="heti-fn">
|
||||
<ol>
|
||||
<li id="fn-01">CSS Reset:指代类似Eric Meyer's Reset CSS的样式重置方案</li>
|
||||
<li id="fn-02">BEM:由Yandex公司创造的命名方式 https://en.bem.info/</li>
|
||||
<li id="fn-03">《中文排版需求》:https://w3c.github.io/clreq/</li>
|
||||
<li id="fn-01">
|
||||
<a href="#ref-01" title="移至">^</a>
|
||||
CSS Reset:指代类似Eric Meyer's Reset CSS的样式重置方案
|
||||
</li>
|
||||
<li id="fn-02">
|
||||
<a href="#ref-02" title="移至">^</a>
|
||||
《中文排版需求》:https://w3c.github.io/clreq/
|
||||
</li>
|
||||
</ol>
|
||||
</footer>
|
||||
</article>
|
||||
|
@ -502,6 +517,7 @@
|
|||
</ul>
|
||||
</aside>
|
||||
|
||||
<script src="./heti-addon.js"></script>
|
||||
<script>
|
||||
const $$root = document.getElementsByTagName('article')[0]
|
||||
|
||||
|
@ -518,6 +534,9 @@
|
|||
addEventListeners(document.getElementsByClassName('J_radioGrid'), 'change', function (e) {
|
||||
$$root.setAttribute('data-bg-grid', e.target.value)
|
||||
})
|
||||
|
||||
const heti = new Heti()
|
||||
heti.autoSpacing()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
0
demo/normalize.css → _site/normalize.css
vendored
0
demo/normalize.css → _site/normalize.css
vendored
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
107
add-ons/add-on.js
Normal file
107
add-ons/add-on.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* Heti add-on v 0.1.0
|
||||
* Add right spacing between CJK & ANS characters
|
||||
*/
|
||||
|
||||
import Finder from 'findandreplacedomtext'
|
||||
|
||||
// 正则表达式来自 pangu.js https://github.com/vinta/pangu.js
|
||||
const CJK = '\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30fa\u30fc-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff'
|
||||
const A = 'A-Za-z\u0370-\u03ff'
|
||||
const N = '0-9'
|
||||
const S = '`~!@#\\$%\\^&\\*\\(\\)-_=\\+\\[\\]{}\\\\\\|;:\'",<.>\\/\\?'
|
||||
const ANS = `${A}${N}${S}`
|
||||
const HETI_NON_CONTIGUOUS_ELEMENTS = {
|
||||
// Block Elements
|
||||
address: 1, article: 1, aside: 1, blockquote: 1, dd: 1, div: 1,
|
||||
dl: 1, fieldset: 1, figcaption: 1, figure: 1, footer: 1, form: 1, h1: 1, h2: 1, h3: 1,
|
||||
h4: 1, h5: 1, h6: 1, header: 1, hgroup: 1, hr: 1, main: 1, nav: 1, noscript: 1, ol: 1,
|
||||
output: 1, p: 1, pre: 1, section: 1, ul: 1,
|
||||
// Other misc. elements that are not part of continuous inline prose:
|
||||
br: 1, li: 1, summary: 1, dt: 1, details: 1, rp: 1, rt: 1, rtc: 1,
|
||||
// Media / Source elements:
|
||||
script: 1, style: 1, img: 1, video: 1, audio: 1, canvas: 1, svg: 1, map: 1, object: 1,
|
||||
// Input elements
|
||||
input: 1, textarea: 1, select: 1, option: 1, optgroup: 1, button: 1,
|
||||
// Table related elements:
|
||||
table: 1, tbody: 1, thead: 1, th: 1, tr: 1, td: 1, caption: 1, col: 1, tfoot: 1, colgroup: 1,
|
||||
// Inline elements
|
||||
ins: 1, del: 1, s: 1,
|
||||
}
|
||||
const HETI_SKIPPED_ELEMENTS = {
|
||||
br: 1, hr: 1,
|
||||
// Media / Source elements:
|
||||
script: 1, style: 1, img: 1, video: 1, audio: 1, canvas: 1, svg: 1, map: 1, object: 1,
|
||||
// Input elements:
|
||||
input: 1, textarea: 1, select: 1, option: 1, optgroup: 1, button: 1,
|
||||
// Pre elements:
|
||||
pre: 1, code: 1, sup: 1, sub: 1,
|
||||
// Heti elements
|
||||
'heti-spacing': 1,
|
||||
}
|
||||
const HETI_SKIPPED_CLASS = 'heti-skip'
|
||||
const hasOwn = {}.hasOwnProperty
|
||||
|
||||
class Heti {
|
||||
constructor (rootSelector) {
|
||||
this.rootSelector = rootSelector || '.heti'
|
||||
this.REG_FULL = new RegExp(`(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`, 'g')
|
||||
this.REG_START = new RegExp(`([${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`, 'g')
|
||||
this.REG_END = new RegExp(`(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)`, 'g')
|
||||
this.funcForceContext = function forceContext (el) {
|
||||
return hasOwn.call(HETI_NON_CONTIGUOUS_ELEMENTS, el.nodeName.toLowerCase())
|
||||
}
|
||||
this.funcFilterElements = function filterElements (el) {
|
||||
return (
|
||||
!(el.classList && el.classList.contains(HETI_SKIPPED_CLASS)) &&
|
||||
!hasOwn.call(HETI_SKIPPED_ELEMENTS, el.nodeName.toLowerCase())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
spacingElements (elmList) {
|
||||
for (let $$root of elmList) {
|
||||
this.spacingElement($$root)
|
||||
}
|
||||
}
|
||||
|
||||
spacingElement ($$elm) {
|
||||
const commonConfig = {
|
||||
forceContext: this.funcForceContext,
|
||||
filterElements: this.funcFilterElements,
|
||||
}
|
||||
const getWrapper = function (classList, text) {
|
||||
const $$r = document.createElement('heti-spacing')
|
||||
$$r.className = classList
|
||||
$$r.textContent = text.trim()
|
||||
return $$r
|
||||
}
|
||||
|
||||
Finder($$elm, Object.assign(commonConfig, {
|
||||
find: this.REG_FULL,
|
||||
replace: portion => getWrapper('heti-spacing-start heti-spacing-end', portion.text),
|
||||
}))
|
||||
|
||||
Finder($$elm, Object.assign(commonConfig, {
|
||||
find: this.REG_START,
|
||||
replace: portion => getWrapper('heti-spacing-start', portion.text),
|
||||
}))
|
||||
|
||||
Finder($$elm, Object.assign(commonConfig, {
|
||||
find: this.REG_END,
|
||||
replace: portion => getWrapper('heti-spacing-end', portion.text),
|
||||
}))
|
||||
}
|
||||
|
||||
autoSpacing () {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $$rootList = document.querySelectorAll(this.rootSelector)
|
||||
|
||||
for (let $$root of $$rootList) {
|
||||
this.spacingElement($$root)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Heti
|
5
demo/heti.min.css
vendored
5
demo/heti.min.css
vendored
File diff suppressed because one or more lines are too long
1
dist/heti-addon.min.js
vendored
Normal file
1
dist/heti-addon.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/heti.min.css
vendored
2
dist/heti.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -23,19 +23,16 @@
|
|||
margin-block-end: $std-block-unit;
|
||||
font-size: $font-size-h1;
|
||||
line-height: $line-height-size-h1;
|
||||
letter-spacing: $font-size-h1 * 0.05;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: $font-size-h2;
|
||||
line-height: $line-height-size-h2;
|
||||
letter-spacing: $font-size-h2 * 0.05;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: $font-size-h3;
|
||||
line-height: $line-height-size-h3;
|
||||
letter-spacing: $font-size-h3 * 0.05;
|
||||
}
|
||||
|
||||
h4 {
|
||||
|
@ -53,6 +50,18 @@
|
|||
line-height: $line-height-size-h6;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
// 中文大标题增加微小文字间距
|
||||
letter-spacing: 0.05em;
|
||||
|
||||
// 非中文时不加间距
|
||||
@include non-cjk-block {
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 压缩两个标题之间的间距
|
||||
h1 + h2,
|
||||
h2 + h3,
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
|
||||
rt {
|
||||
font-size: 0.875em;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
|
||||
// 完美 <small> 字号 by Sivan
|
||||
|
@ -111,6 +111,7 @@
|
|||
margin-inline-end: 0.25em;
|
||||
font-size: 0.75em;
|
||||
font-family: $font-family-hei;
|
||||
font-style: normal;
|
||||
line-height: 1;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
@ -121,6 +122,11 @@
|
|||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
|
||||
&:target,
|
||||
a:target {
|
||||
background-color: #def;
|
||||
}
|
||||
}
|
||||
|
||||
summary {
|
||||
|
@ -155,7 +161,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
// 为所有加划线的元素增加缝隙
|
||||
// 为带划线的元素添加间距,以防止视觉上混为一个元素
|
||||
// 注: 如果设成为两个相连元素添加间距,会有一个问题:
|
||||
// 如果结构是 `<u>倚天</u>和<u>屠龙</u>`,「屠龙」前面仍然会有边距。
|
||||
// 此处跟预期不一致,应该只在两个同名元素紧邻时增加边距,即:`<u>倚天剑</u><u>屠龙刀</u>`
|
||||
//@each $tag in (abbr[title], del, ins, s, u) {
|
||||
// #{$tag} + #{$tag} {
|
||||
// margin-inline-start: 0.125em;
|
||||
// }
|
||||
//}
|
||||
// 因此采用下面的形式,为所有加划线的元素增加缝隙
|
||||
abbr[title],
|
||||
del,
|
||||
ins,
|
||||
|
@ -164,12 +179,4 @@
|
|||
margin-inline-start: 1px;
|
||||
margin-inline-end: 1px;
|
||||
}
|
||||
|
||||
// 带边框的元素,两个相连时添加间距,以防止视觉上混为一个元素
|
||||
// @todo: 此处有一个问题:如果结构是 <u>倚天</u>和<u>屠龙</u>,「屠龙」前面仍然会有边距。此处跟预期不一致,应该只在两个同名元素紧邻时增加边距,即:<u>倚天剑</u><u>屠龙刀</u>
|
||||
//@each $tag in (abbr[title], del, ins, s, u) {
|
||||
// #{$tag} + #{$tag} {
|
||||
// margin-inline-start: 0.125em;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
|
23
lib/helpers/_add-on.scss
Normal file
23
lib/helpers/_add-on.scss
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Author: Sivan [sun.sivan@gmail.com]
|
||||
// Description: define add-ons.
|
||||
@import "../variables";
|
||||
|
||||
@mixin hetiAddOns {
|
||||
// 用于中西文混排增加边距
|
||||
heti-spacing {
|
||||
display: inline;
|
||||
|
||||
& + sup,
|
||||
& + sub {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.heti-spacing-start {
|
||||
margin-inline-end: 0.25em;
|
||||
}
|
||||
|
||||
.heti-spacing-end {
|
||||
margin-inline-start: 0.25em;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
&-x-large {
|
||||
font-size: $font-size-x-large;
|
||||
line-height: $line-height-size-x-large;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
&-small {
|
||||
|
@ -78,5 +79,11 @@
|
|||
margin-block-start: $std-block-unit / 2;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
&:target {
|
||||
background-color: #def;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
@import "../variables";
|
||||
|
||||
@mixin hetiInlineHelperClasses {
|
||||
// 标点悬挂 Punctuation mark hanging
|
||||
// @todo: 用于标点悬挂用的样式
|
||||
#{$root-selector}-hang {
|
||||
@include hang();
|
||||
}
|
||||
|
||||
// 显示强烈的着重号
|
||||
// 强烈着重号 Emphasis Mark
|
||||
#{$root-selector}-em {
|
||||
-webkit-text-emphasis: filled circle;
|
||||
-webkit-text-emphasis-position: under;
|
||||
|
@ -17,4 +18,21 @@
|
|||
-webkit-text-emphasis: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 内联 Ruby
|
||||
// 在非行间注排版中使用 ruby 标签,且不额外占据空间
|
||||
// 注:使用此样式需按字拆分 <ruby class="heti-ruby heti-ruby--inline"><rb>赫</rb><rp>(</rp><rt>hè</rt><rp>)</rp></ruby>
|
||||
// https://stackoverflow.com/questions/38680695/adjust-the-vertical-positioning-of-ruby-text/38877801#38877801
|
||||
#{$root-selector}-ruby {
|
||||
&--inline {
|
||||
display: inline-flex;
|
||||
flex-direction: column-reverse;
|
||||
height: 1.5em;
|
||||
|
||||
rt {
|
||||
display: inline;
|
||||
line-height: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
@import "modifiers/annotation";
|
||||
@import "helpers/block";
|
||||
@import "helpers/inline";
|
||||
@import "helpers/add-on";
|
||||
|
||||
#{$root-selector} {
|
||||
// 中文每行展示文字(CPL)建议在 30~50 之间,默认 42
|
||||
|
@ -25,6 +26,7 @@
|
|||
// 默认字体大小为 16px,行高 1.5
|
||||
font-size: $font-size-normal;
|
||||
font-weight: $font-weight-normal;
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
line-height: $line-height-normal;
|
||||
|
||||
// 自动在中西文间加 1/4 空格(暂无浏览器支持)
|
||||
|
@ -52,4 +54,5 @@
|
|||
// .heti .heti-verse, .heti .heti-hang
|
||||
@include hetiBlockHelperClasses();
|
||||
@include hetiInlineHelperClasses();
|
||||
@include hetiAddOns();
|
||||
}
|
||||
|
|
395
package-lock.json
generated
395
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "heti",
|
||||
"version": "0.1.0",
|
||||
"version": "0.4.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -214,12 +214,53 @@
|
|||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-commonjs": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.2.tgz",
|
||||
"integrity": "sha512-MPYGZr0qdbV5zZj8/2AuomVpnRVXRU5XKXb3HVniwRoRCreGlf5kOE081isNWeiLIi6IYkwTX9zE0/c7V8g81g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.0",
|
||||
"estree-walker": "^1.0.1",
|
||||
"is-reference": "^1.1.2",
|
||||
"magic-string": "^0.25.2",
|
||||
"resolve": "^1.11.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz",
|
||||
"integrity": "sha512-14ddhD7TnemeHE97a4rLOhobfYvUVcaYuqTnL8Ti7Jxi9V9Jr5LY7Gko4HZ5k4h4vqQM0gBQt6tsp9xXW94WPA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.0.6",
|
||||
"@types/resolve": "0.0.8",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.14.2"
|
||||
}
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.8.tgz",
|
||||
"integrity": "sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estree-walker": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
|
||||
|
@ -244,6 +285,15 @@
|
|||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/resolve": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
|
||||
"integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/unist": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
|
||||
|
@ -276,6 +326,12 @@
|
|||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
|
||||
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
|
||||
"dev": true
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz",
|
||||
|
@ -369,12 +425,6 @@
|
|||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true
|
||||
},
|
||||
"array-uniq": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
|
||||
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
|
||||
"dev": true
|
||||
},
|
||||
"arrify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
|
@ -402,15 +452,6 @@
|
|||
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"async-foreach": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
|
||||
|
@ -523,6 +564,18 @@
|
|||
"node-releases": "^1.1.46"
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
|
||||
"dev": true
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
|
||||
"integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
|
||||
"dev": true
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
|
@ -949,12 +1002,6 @@
|
|||
"integrity": "sha512-C1i/vH6/kQx9YV8RddMkmW216GwW4pTrnYIlKmDFIqXA4fPwqDxIdGyHsuG+fgurHoljRz7/oaD+tztcryW/9g==",
|
||||
"dev": true
|
||||
},
|
||||
"email-addresses": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz",
|
||||
"integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
|
@ -982,6 +1029,12 @@
|
|||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"estree-walker": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
|
||||
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
|
||||
"dev": true
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
|
@ -1052,33 +1105,6 @@
|
|||
"flat-cache": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"filename-reserved-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz",
|
||||
"integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=",
|
||||
"dev": true
|
||||
},
|
||||
"filenamify": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz",
|
||||
"integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"filename-reserved-regex": "^1.0.0",
|
||||
"strip-outer": "^1.0.0",
|
||||
"trim-repeated": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"filenamify-url": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz",
|
||||
"integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"filenamify": "^1.0.0",
|
||||
"humanize-url": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
|
@ -1098,6 +1124,11 @@
|
|||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"findandreplacedomtext": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/findandreplacedomtext/-/findandreplacedomtext-0.4.6.tgz",
|
||||
"integrity": "sha512-CVRIKbEwfWoKTSnLrmyX26WjMY7o0aUUTm0RXN47fF/eHINJa8C+YHZxGagC7gMWVaAEBFwH7uVXuhwZcylWOQ=="
|
||||
},
|
||||
"flat-cache": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
|
||||
|
@ -1132,17 +1163,6 @@
|
|||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
@ -1250,44 +1270,6 @@
|
|||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"gh-pages": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz",
|
||||
"integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "^2.6.1",
|
||||
"commander": "^2.18.0",
|
||||
"email-addresses": "^3.0.1",
|
||||
"filenamify-url": "^1.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"globby": "^6.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"array-union": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
||||
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-uniq": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"globby": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-union": "^1.0.1",
|
||||
"glob": "^7.0.3",
|
||||
"object-assign": "^4.0.1",
|
||||
"pify": "^2.0.0",
|
||||
"pinkie-promise": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
|
@ -1479,16 +1461,6 @@
|
|||
"sshpk": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"humanize-url": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz",
|
||||
"integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"normalize-url": "^1.0.0",
|
||||
"strip-url-auth": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
|
||||
|
@ -1647,6 +1619,12 @@
|
|||
"integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==",
|
||||
"dev": true
|
||||
},
|
||||
"is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
|
||||
"dev": true
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
|
@ -1665,6 +1643,15 @@
|
|||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
||||
"dev": true
|
||||
},
|
||||
"is-reference": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
|
||||
"integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "0.0.39"
|
||||
}
|
||||
},
|
||||
"is-regexp": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz",
|
||||
|
@ -1713,6 +1700,27 @@
|
|||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
|
||||
"dev": true
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz",
|
||||
"integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^6.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"supports-color": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
|
||||
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
|
||||
|
@ -1778,15 +1786,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||
|
@ -1937,6 +1936,15 @@
|
|||
"yallist": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.6.tgz",
|
||||
"integrity": "sha512-3a5LOMSGoCTH5rbqobC2HuDNRtE2glHZ8J7pK+QZYppyWA36yuNpsX994rIY2nCuyP7CZYy7lQq/X2jygiZ89g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"map-obj": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz",
|
||||
|
@ -1989,6 +1997,12 @@
|
|||
"yargs-parser": "^16.1.0"
|
||||
}
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"merge2": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
|
||||
|
@ -2354,18 +2368,6 @@
|
|||
"integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-url": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
|
||||
"integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"object-assign": "^4.0.1",
|
||||
"prepend-http": "^1.0.0",
|
||||
"query-string": "^4.1.0",
|
||||
"sort-keys": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||
|
@ -2736,12 +2738,6 @@
|
|||
"integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"prepend-http": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
|
||||
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
|
||||
"dev": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
|
@ -2772,16 +2768,6 @@
|
|||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
|
||||
"dev": true
|
||||
},
|
||||
"query-string": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
|
||||
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"object-assign": "^4.1.0",
|
||||
"strict-uri-encode": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"quick-lru": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
|
||||
|
@ -2993,6 +2979,47 @@
|
|||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.0.tgz",
|
||||
"integrity": "sha512-ab2tF5pdDqm2zuI8j02ceyrJSScl9V2C24FgWQ1v1kTFTu1UrG5H0hpP++mDZlEFyZX4k0chtGEHU2i+pAzBgA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "*",
|
||||
"@types/node": "*",
|
||||
"acorn": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-terser": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.2.0.tgz",
|
||||
"integrity": "sha512-jQI+nYhtDBc9HFRBz8iGttQg7li9klmzR62RG2W2nN6hJ/FI2K2ItYQ7kJ7/zn+vs+BP1AEccmVRjRN989I+Nw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"jest-worker": "^24.9.0",
|
||||
"rollup-pluginutils": "^2.8.2",
|
||||
"serialize-javascript": "^2.1.2",
|
||||
"terser": "^4.6.2"
|
||||
}
|
||||
},
|
||||
"rollup-pluginutils": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
|
||||
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estree-walker": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"estree-walker": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
|
||||
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"run-parallel": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
|
||||
|
@ -3050,6 +3077,12 @@
|
|||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
|
||||
"dev": true
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
|
@ -3087,21 +3120,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"sort-keys": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-plain-obj": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
|
||||
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"dev": true
|
||||
},
|
||||
"spdx-correct": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
|
||||
|
@ -3204,12 +3244,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
|
||||
|
@ -3269,21 +3303,6 @@
|
|||
"min-indent": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"strip-outer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
|
||||
"integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"strip-url-auth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz",
|
||||
"integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=",
|
||||
"dev": true
|
||||
},
|
||||
"style-search": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
|
||||
|
@ -3485,6 +3504,17 @@
|
|||
"inherits": "2"
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "4.6.4",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.4.tgz",
|
||||
"integrity": "sha512-5fqgBPLgVHZ/fVvqRhhUp9YUiGXhFJ9ZkrZWD9vQtFBR4QIGTnbsb+/kKqSqfgp3WnBwGWAFnedGTtmX1YTn0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
"source-map-support": "~0.5.12"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
|
@ -3530,15 +3560,6 @@
|
|||
"integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==",
|
||||
"dev": true
|
||||
},
|
||||
"trim-repeated": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
|
||||
"integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"trim-trailing-lines": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz",
|
||||
|
@ -3673,12 +3694,6 @@
|
|||
"unist-util-is": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||
|
|
44
package.json
44
package.json
|
@ -1,17 +1,18 @@
|
|||
{
|
||||
"name": "heti",
|
||||
"version": "0.3.4",
|
||||
"version": "0.4.0",
|
||||
"description": "赫蹏是专为中文内容展示设计的排版样式增强。它基于通行的中文排版规范而来,可以为网站的读者带来更好的文章阅读体验。",
|
||||
"main": "lib/heti.scss",
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"add-ons",
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "node-sass -w --output-style=compressed lib/heti.scss demo/heti.min.css",
|
||||
"build:expanded": "node-sass lib/heti.scss dist/heti.css --output-style=expanded",
|
||||
"build:compressed": "node-sass lib/heti.scss dist/heti.min.css --output-style=compressed",
|
||||
"build": "npm run build:expanded && npm run build:compressed",
|
||||
"deploy": "gh-pages -d demo",
|
||||
"start": "node-sass -w --output-style=expanded lib/heti.scss _site/heti.css",
|
||||
"compile": "rollup -c -w",
|
||||
"build:style": "node-sass lib/heti.scss dist/heti.min.css --output-style=compressed",
|
||||
"build:script": "rollup -c",
|
||||
"build": "npm run build:style && npm run build:script",
|
||||
"test": "npx stylelint --config package.json 'lib/**/*.scss'"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -31,21 +32,40 @@
|
|||
"url": "https://github.com/sivan/heti/issues"
|
||||
},
|
||||
"homepage": "https://github.com/sivan/heti#readme",
|
||||
"dependencies": {
|
||||
"findandreplacedomtext": "^0.4.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "^2.2.0",
|
||||
"@rollup/plugin-commonjs": "^11.0.2",
|
||||
"@rollup/plugin-node-resolve": "^7.1.1",
|
||||
"node-sass": "^4.13.1",
|
||||
"rollup": "^1.32.0",
|
||||
"rollup-plugin-terser": "^5.2.0",
|
||||
"stylelint": "^13.0.0",
|
||||
"stylelint-config-recommended-scss": "^4.1.0",
|
||||
"stylelint-config-standard": "^19.0.0",
|
||||
"stylelint-scss": "^3.13.0"
|
||||
},
|
||||
"stylelint": {
|
||||
"extends": ["stylelint-config-standard", "stylelint-config-recommended-scss"],
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-recommended-scss"
|
||||
],
|
||||
"rules": {
|
||||
"no-descending-specificity": [
|
||||
true,
|
||||
{
|
||||
"ignore": ["selectors-within-list"]
|
||||
"ignore": [
|
||||
"selectors-within-list"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selector-type-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignore": [
|
||||
"custom-elements"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
30
rollup.config.js
Normal file
30
rollup.config.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import {terser} from 'rollup-plugin-terser';
|
||||
|
||||
export default {
|
||||
input: 'add-ons/add-on.js',
|
||||
output: [
|
||||
{
|
||||
file: '_site/heti-addon.js',
|
||||
name: 'Heti',
|
||||
format: 'umd'
|
||||
},
|
||||
{
|
||||
file: 'dist/heti-addon.min.js',
|
||||
format: 'umd',
|
||||
name: 'Heti',
|
||||
plugins: [
|
||||
terser({
|
||||
output: {
|
||||
comments: false
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
resolve(),
|
||||
commonjs(),
|
||||
]
|
||||
};
|
Loading…
Reference in a new issue