Deploying to gh-pages from - 35dbe6f7e6a81742881494fad77c8f5c8e3b6e96 🚀
This commit is contained in:
parent
34c86b7204
commit
54031dbe90
3 changed files with 841 additions and 64 deletions
753
heti-addon.js
Normal file
753
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;
|
||||
|
||||
})));
|
13
index.css
13
index.css
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
139
index.html
139
index.html
|
@ -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,27 +65,27 @@
|
|||
<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">
|
||||
|
@ -120,12 +119,12 @@
|
|||
</div>
|
||||
<hr>
|
||||
<div class="heti--poetry">
|
||||
<h2>丑奴儿·书博山道中壁<span class="heti-meta heti-small">[宋]<abbr title="号稼轩">辛弃疾</abbr></span></h2>
|
||||
<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>
|
||||
|
@ -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>
|
||||
|
@ -206,12 +205,12 @@
|
|||
|
||||
<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">
|
||||
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue