Archive prototype outside main tree
This commit is contained in:
@@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
speech_cache
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
GET https://api.elevenlabs.io/v1/voices
|
|
||||||
xi-api-key: d191e27c2e5b07573b39fe70f0783f48
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Vendored
-931
@@ -1,931 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license Hyphenopoly 5.2.0-beta.1 - client side hyphenation for webbrowsers
|
|
||||||
* ©2023 Mathias Nater, Güttingen (mathiasnater at gmail dot com)
|
|
||||||
* https://github.com/mnater/Hyphenopoly
|
|
||||||
*
|
|
||||||
* Released under the MIT license
|
|
||||||
* http://mnater.github.io/Hyphenopoly/LICENSE
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* globals Hyphenopoly:readonly */
|
|
||||||
((w, o) => {
|
|
||||||
"use strict";
|
|
||||||
const SOFTHYPHEN = "\u00AD";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event
|
|
||||||
*/
|
|
||||||
const event = ((H) => {
|
|
||||||
const knownEvents = new Map([
|
|
||||||
["afterElementHyphenation", []],
|
|
||||||
["beforeElementHyphenation", []],
|
|
||||||
["engineReady", []],
|
|
||||||
[
|
|
||||||
"error", [
|
|
||||||
(e) => {
|
|
||||||
if (e.runDefault) {
|
|
||||||
w.console.warn(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
["hyphenopolyEnd", []],
|
|
||||||
["hyphenopolyStart", []]
|
|
||||||
]);
|
|
||||||
if (H.hev) {
|
|
||||||
const userEvents = new Map(o.entries(H.hev));
|
|
||||||
knownEvents.forEach((eventFuncs, eventName) => {
|
|
||||||
if (userEvents.has(eventName)) {
|
|
||||||
eventFuncs.unshift(userEvents.get(eventName));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
"fire": ((eventName, eventData) => {
|
|
||||||
eventData.runDefault = true;
|
|
||||||
eventData.preventDefault = () => {
|
|
||||||
eventData.runDefault = false;
|
|
||||||
};
|
|
||||||
knownEvents.get(eventName).forEach((eventFn) => {
|
|
||||||
eventFn(eventData);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
};
|
|
||||||
})(Hyphenopoly);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register copy event on element
|
|
||||||
* @param {Object} el The element
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
function registerOnCopy(el) {
|
|
||||||
el.addEventListener(
|
|
||||||
"copy",
|
|
||||||
(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const sel = w.getSelection();
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.appendChild(sel.getRangeAt(0).cloneContents());
|
|
||||||
e.clipboardData.setData("text/plain", sel.toString().replace(RegExp(SOFTHYPHEN, "g"), ""));
|
|
||||||
e.clipboardData.setData("text/html", div.innerHTML.replace(RegExp(SOFTHYPHEN, "g"), ""));
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert settings from H.setup-Object to Map
|
|
||||||
* This is a IIFE to keep complexity low.
|
|
||||||
*/
|
|
||||||
((H) => {
|
|
||||||
/**
|
|
||||||
* Create a Map with a default Map behind the scenes. This mimics
|
|
||||||
* kind of a prototype chain of an object, but without the object-
|
|
||||||
* injection security risk.
|
|
||||||
*
|
|
||||||
* @param {Map} defaultsMap - A Map with default values
|
|
||||||
* @returns {Proxy} - A Proxy for the Map (dot-notation or get/set)
|
|
||||||
*/
|
|
||||||
function createMapWithDefaults(defaultsMap) {
|
|
||||||
const userMap = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The get-trap: get the value from userMap or else from defaults
|
|
||||||
* @param {Sring} key - The key to retrieve the value for
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
function get(key) {
|
|
||||||
return (userMap.has(key))
|
|
||||||
? userMap.get(key)
|
|
||||||
: defaultsMap.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The set-trap: set the value to userMap and don't touch defaults
|
|
||||||
* @param {Sring} key - The key for the value
|
|
||||||
* @param {*} value - The value
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
function set(key, value) {
|
|
||||||
userMap.set(key, value);
|
|
||||||
}
|
|
||||||
return new Proxy(defaultsMap, {
|
|
||||||
"get": (_target, prop) => {
|
|
||||||
if (prop === "set") {
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
if (prop === "get") {
|
|
||||||
return get;
|
|
||||||
}
|
|
||||||
return get(prop);
|
|
||||||
},
|
|
||||||
"ownKeys": () => {
|
|
||||||
return [
|
|
||||||
...new Set(
|
|
||||||
[...defaultsMap.keys(), ...userMap.keys()]
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = createMapWithDefaults(new Map([
|
|
||||||
["defaultLanguage", "en-us"],
|
|
||||||
[
|
|
||||||
"dontHyphenate", (() => {
|
|
||||||
const list = "abbr,acronym,audio,br,button,code,img,input,kbd,label,math,option,pre,samp,script,style,sub,sup,svg,textarea,var,video";
|
|
||||||
return createMapWithDefaults(
|
|
||||||
new Map(list.split(",").map((val) => {
|
|
||||||
return [val, true];
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
})()
|
|
||||||
],
|
|
||||||
["dontHyphenateClass", "donthyphenate"],
|
|
||||||
["exceptions", new Map()],
|
|
||||||
["keepAlive", true],
|
|
||||||
["normalize", false],
|
|
||||||
["processShadows", false],
|
|
||||||
["safeCopy", true],
|
|
||||||
["substitute", new Map()],
|
|
||||||
["timeout", 1000]
|
|
||||||
]));
|
|
||||||
o.entries(H.s).forEach(([key, value]) => {
|
|
||||||
switch (key) {
|
|
||||||
case "selectors":
|
|
||||||
// Set settings.selectors to array of selectors
|
|
||||||
settings.set("selectors", o.keys(value));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For each selector add a property to settings with
|
|
||||||
* selector specific settings
|
|
||||||
*/
|
|
||||||
o.entries(value).forEach(([sel, selSettings]) => {
|
|
||||||
const selectorSettings = createMapWithDefaults(new Map([
|
|
||||||
["compound", "hyphen"],
|
|
||||||
["hyphen", SOFTHYPHEN],
|
|
||||||
["leftmin", 0],
|
|
||||||
["leftminPerLang", 0],
|
|
||||||
["minWordLength", 6],
|
|
||||||
["mixedCase", true],
|
|
||||||
["orphanControl", 1],
|
|
||||||
["rightmin", 0],
|
|
||||||
["rightminPerLang", 0]
|
|
||||||
]));
|
|
||||||
o.entries(selSettings).forEach(
|
|
||||||
([selSetting, setVal]) => {
|
|
||||||
if (typeof setVal === "object") {
|
|
||||||
selectorSettings.set(
|
|
||||||
selSetting,
|
|
||||||
new Map(o.entries(setVal))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
selectorSettings.set(selSetting, setVal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
settings.set(sel, selectorSettings);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "dontHyphenate":
|
|
||||||
case "exceptions":
|
|
||||||
o.entries(value).forEach(([k, v]) => {
|
|
||||||
settings.get(key).set(k, v);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "substitute":
|
|
||||||
o.entries(value).forEach(([lang, subst]) => {
|
|
||||||
settings.substitute.set(
|
|
||||||
lang,
|
|
||||||
new Map(o.entries(subst))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
settings.set(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
H.c = settings;
|
|
||||||
})(Hyphenopoly);
|
|
||||||
|
|
||||||
((H) => {
|
|
||||||
const C = H.c;
|
|
||||||
let mainLanguage = null;
|
|
||||||
|
|
||||||
event.fire(
|
|
||||||
"hyphenopolyStart",
|
|
||||||
{
|
|
||||||
"msg": "hyphenopolyStart"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for elements
|
|
||||||
* @returns {Object} elements-object
|
|
||||||
*/
|
|
||||||
function makeElementCollection() {
|
|
||||||
const list = new Map();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Counter counts the elements to be hyphenated.
|
|
||||||
* Needs to be an object (Pass by reference)
|
|
||||||
*/
|
|
||||||
const counter = [0];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add element to elements
|
|
||||||
* @param {object} el The element
|
|
||||||
* @param {string} lang The language of the element
|
|
||||||
* @param {string} sel The selector of the element
|
|
||||||
* @returns {Object} An element-object
|
|
||||||
*/
|
|
||||||
function add(el, lang, sel) {
|
|
||||||
const elo = {
|
|
||||||
"element": el,
|
|
||||||
"selector": sel
|
|
||||||
};
|
|
||||||
if (!list.has(lang)) {
|
|
||||||
list.set(lang, []);
|
|
||||||
}
|
|
||||||
list.get(lang).push(elo);
|
|
||||||
counter[0] += 1;
|
|
||||||
return elo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes elements from the list and updates the counter
|
|
||||||
* @param {string} lang - The lang of the elements to remove
|
|
||||||
*/
|
|
||||||
function rem(lang) {
|
|
||||||
let langCount = 0;
|
|
||||||
if (list.has(lang)) {
|
|
||||||
langCount = list.get(lang).length;
|
|
||||||
list.delete(lang);
|
|
||||||
counter[0] -= langCount;
|
|
||||||
if (counter[0] === 0) {
|
|
||||||
event.fire(
|
|
||||||
"hyphenopolyEnd",
|
|
||||||
{
|
|
||||||
"msg": "hyphenopolyEnd"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!C.keepAlive) {
|
|
||||||
window.Hyphenopoly = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
add,
|
|
||||||
counter,
|
|
||||||
list,
|
|
||||||
rem
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get language of element by searching its parents or fallback
|
|
||||||
* @param {Object} el The element
|
|
||||||
* @param {string} parentLang Lang of parent if available
|
|
||||||
* @param {boolean} fallback Will falback to mainlanguage
|
|
||||||
* @returns {string|null} The language or null
|
|
||||||
*/
|
|
||||||
function getLang(el, parentLang = "", fallback = true) {
|
|
||||||
// Find closest el with lang attr not empty
|
|
||||||
el = el.closest("[lang]:not([lang=''])");
|
|
||||||
if (el && el.lang) {
|
|
||||||
return el.lang.toLowerCase();
|
|
||||||
}
|
|
||||||
if (parentLang) {
|
|
||||||
return parentLang;
|
|
||||||
}
|
|
||||||
return (fallback)
|
|
||||||
? mainLanguage
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect elements that have a selector defined in C.selectors
|
|
||||||
* and add them to elements.
|
|
||||||
* @param {Object} [parent = null] The start point element
|
|
||||||
* @param {string} [selector = null] The selector matching the parent
|
|
||||||
* @returns {Object} elements-object
|
|
||||||
*/
|
|
||||||
function collectElements(parent = null, selector = null) {
|
|
||||||
const elements = makeElementCollection();
|
|
||||||
|
|
||||||
const dontHyphenateSelector = (() => {
|
|
||||||
let s = "." + C.dontHyphenateClass;
|
|
||||||
o.getOwnPropertyNames(C.dontHyphenate).forEach((tag) => {
|
|
||||||
if (C.dontHyphenate.get(tag)) {
|
|
||||||
s += "," + tag;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return s;
|
|
||||||
})();
|
|
||||||
const matchingSelectors = C.selectors.join(",") + "," + dontHyphenateSelector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively walk all elements in el, lending lang and selName
|
|
||||||
* add them to elements if necessary.
|
|
||||||
* @param {Object} el The element to scan
|
|
||||||
* @param {string} pLang The language of the parent element
|
|
||||||
* @param {string} sel The selector of the parent element
|
|
||||||
* @param {boolean} isChild If el is a child element
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
function processElements(el, pLang, sel, isChild = false) {
|
|
||||||
const eLang = getLang(el, pLang);
|
|
||||||
const langDef = H.cf.langs.get(eLang);
|
|
||||||
if (langDef === "H9Y") {
|
|
||||||
elements.add(el, eLang, sel);
|
|
||||||
if (!isChild && C.safeCopy) {
|
|
||||||
registerOnCopy(el);
|
|
||||||
}
|
|
||||||
} else if (!langDef && eLang !== "zxx") {
|
|
||||||
event.fire(
|
|
||||||
"error",
|
|
||||||
Error(`Element with '${eLang}' found, but '${eLang}.wasm' not loaded. Check language tags!`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
el.childNodes.forEach((n) => {
|
|
||||||
if (n.nodeType === 1 && !n.matches(matchingSelectors)) {
|
|
||||||
processElements(n, eLang, sel, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches the DOM for each sel
|
|
||||||
* @param {object} root The DOM root
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
function getElems(root) {
|
|
||||||
C.selectors.forEach((sel) => {
|
|
||||||
root.querySelectorAll(sel).forEach((n) => {
|
|
||||||
processElements(n, getLang(n), sel, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent === null) {
|
|
||||||
if (C.processShadows) {
|
|
||||||
w.document.querySelectorAll("*").forEach((m) => {
|
|
||||||
if (m.shadowRoot) {
|
|
||||||
getElems(m.shadowRoot);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
getElems(w.document);
|
|
||||||
} else {
|
|
||||||
processElements(parent, getLang(parent), selector);
|
|
||||||
}
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wordHyphenatorPool = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for hyphenatorFunctions for a specific language and selector
|
|
||||||
* @param {Object} lo Language-Object
|
|
||||||
* @param {string} lang The language
|
|
||||||
* @param {string} sel The selector
|
|
||||||
* @returns {function} The hyphenate function
|
|
||||||
*/
|
|
||||||
function createWordHyphenator(lo, lang, sel) {
|
|
||||||
const poolKey = lang + "-" + sel;
|
|
||||||
if (wordHyphenatorPool.has(poolKey)) {
|
|
||||||
return wordHyphenatorPool.get(poolKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
const selSettings = C.get(sel);
|
|
||||||
lo.cache.set(sel, new Map());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HyphenateFunction for non-compound words
|
|
||||||
* @param {string} word The word
|
|
||||||
* @returns {string} The hyphenated word
|
|
||||||
*/
|
|
||||||
function hyphenateNormal(word) {
|
|
||||||
if (word.length > 61) {
|
|
||||||
event.fire(
|
|
||||||
"error",
|
|
||||||
Error("Found word longer than 61 characters")
|
|
||||||
);
|
|
||||||
} else if (!lo.reNotAlphabet.test(word)) {
|
|
||||||
return lo.hyphenate(
|
|
||||||
word,
|
|
||||||
selSettings.hyphen.charCodeAt(0),
|
|
||||||
selSettings.leftminPerLang.get(lang),
|
|
||||||
selSettings.rightminPerLang.get(lang)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HyphenateFunction for compound words
|
|
||||||
* @param {string} word The word
|
|
||||||
* @returns {string} The hyphenated compound word
|
|
||||||
*/
|
|
||||||
function hyphenateCompound(word) {
|
|
||||||
const zeroWidthSpace = "\u200B";
|
|
||||||
let parts = null;
|
|
||||||
let wordHyphenator = null;
|
|
||||||
if (selSettings.compound === "auto" ||
|
|
||||||
selSettings.compound === "all") {
|
|
||||||
wordHyphenator = createWordHyphenator(lo, lang, sel);
|
|
||||||
parts = word.split("-").map((p) => {
|
|
||||||
if (p.length >= selSettings.minWordLength) {
|
|
||||||
return wordHyphenator(p);
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
});
|
|
||||||
if (selSettings.compound === "auto") {
|
|
||||||
word = parts.join("-");
|
|
||||||
} else {
|
|
||||||
word = parts.join("-" + zeroWidthSpace);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
word = word.replace("-", "-" + zeroWidthSpace);
|
|
||||||
}
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a string is mixed case
|
|
||||||
* @param {string} s The string
|
|
||||||
* @returns {boolean} true if s is mixed case
|
|
||||||
*/
|
|
||||||
function isMixedCase(s) {
|
|
||||||
return [...s].map((c) => {
|
|
||||||
return (c === c.toLowerCase());
|
|
||||||
}).some((v, i, a) => {
|
|
||||||
return (v !== a[0]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HyphenateFunction for words (compound or not)
|
|
||||||
* @param {string} word The word
|
|
||||||
* @returns {string} The hyphenated word
|
|
||||||
*/
|
|
||||||
function hyphenator(word) {
|
|
||||||
let hw = lo.cache.get(sel).get(word);
|
|
||||||
if (!hw) {
|
|
||||||
if (lo.exc.has(word)) {
|
|
||||||
hw = lo.exc.get(word).replace(
|
|
||||||
/-/g,
|
|
||||||
selSettings.hyphen
|
|
||||||
);
|
|
||||||
} else if (!selSettings.mixedCase && isMixedCase(word)) {
|
|
||||||
hw = word;
|
|
||||||
} else if (word.indexOf("-") === -1) {
|
|
||||||
hw = hyphenateNormal(word);
|
|
||||||
} else {
|
|
||||||
hw = hyphenateCompound(word);
|
|
||||||
}
|
|
||||||
lo.cache.get(sel).set(word, hw);
|
|
||||||
}
|
|
||||||
return hw;
|
|
||||||
}
|
|
||||||
wordHyphenatorPool.set(poolKey, hyphenator);
|
|
||||||
return hyphenator;
|
|
||||||
}
|
|
||||||
|
|
||||||
const orphanControllerPool = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for function that handles orphans
|
|
||||||
* @param {string} sel The selector
|
|
||||||
* @returns {function} The function created
|
|
||||||
*/
|
|
||||||
function createOrphanController(sel) {
|
|
||||||
if (orphanControllerPool.has(sel)) {
|
|
||||||
return orphanControllerPool.get(sel);
|
|
||||||
}
|
|
||||||
const selSettings = C.get(sel);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function template
|
|
||||||
* @param {string} ignore unused result of replace
|
|
||||||
* @param {string} leadingWhiteSpace The leading whiteSpace
|
|
||||||
* @param {string} lastWord The last word
|
|
||||||
* @param {string} trailingWhiteSpace The trailing whiteSpace
|
|
||||||
* @returns {string} Treated end of text
|
|
||||||
*/
|
|
||||||
function controlOrphans(
|
|
||||||
ignore,
|
|
||||||
leadingWhiteSpace,
|
|
||||||
lastWord,
|
|
||||||
trailingWhiteSpace
|
|
||||||
) {
|
|
||||||
if (selSettings.orphanControl === 3 && leadingWhiteSpace === " ") {
|
|
||||||
// \u00A0 = no-break space (nbsp)
|
|
||||||
leadingWhiteSpace = "\u00A0";
|
|
||||||
}
|
|
||||||
return leadingWhiteSpace + lastWord.replace(RegExp(selSettings.hyphen, "g"), "") + trailingWhiteSpace;
|
|
||||||
}
|
|
||||||
orphanControllerPool.set(sel, controlOrphans);
|
|
||||||
return controlOrphans;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wordRegExpPool = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hyphenate an entitiy (text string or Element-Object)
|
|
||||||
* @param {string} lang - the language of the string
|
|
||||||
* @param {string} sel - the selectorName of settings
|
|
||||||
* @param {string} entity - the entity to be hyphenated
|
|
||||||
* @returns {string | null} hyphenated str according to setting of sel
|
|
||||||
*/
|
|
||||||
function hyphenate(lang, sel, entity) {
|
|
||||||
const lo = H.languages.get(lang);
|
|
||||||
const selSettings = C.get(sel);
|
|
||||||
const minWordLength = selSettings.minWordLength;
|
|
||||||
|
|
||||||
|
|
||||||
const regExpWord = (() => {
|
|
||||||
const key = lang + minWordLength;
|
|
||||||
if (wordRegExpPool.has(key)) {
|
|
||||||
return wordRegExpPool.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Transpiled RegExp of
|
|
||||||
* /[${alphabet}\p{Mn}Subset\p{Letter}\00AD-]
|
|
||||||
* {${minwordlength},}/gui
|
|
||||||
*/
|
|
||||||
const reWord = RegExp(
|
|
||||||
`[${lo.alphabet}a-z\u0300-\u036F\u0483-\u0487\u00DF-\u00F6\u00F8-\u00FE\u0101\u0103\u0105\u0107\u0109\u010D\u010F\u0111\u0113\u0117\u0119\u011B\u011D\u011F\u0123\u0125\u012B\u012F\u0131\u0135\u0137\u013C\u013E\u0142\u0144\u0146\u0148\u014D\u0151\u0153\u0155\u0159\u015B\u015D\u015F\u0161\u0165\u016B\u016D\u016F\u0171\u0173\u017A\u017C\u017E\u017F\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u0219\u021B\u02BC\u0390\u03AC-\u03CE\u03D0\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF\u03F2\u0430-\u044F\u0451-\u045C\u045E\u045F\u0491\u04AF\u04E9\u0561-\u0585\u0587\u0905-\u090C\u090F\u0910\u0913-\u0928\u092A-\u0930\u0932\u0933\u0935-\u0939\u093D\u0960\u0961\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A85-\u0A8B\u0A8F\u0A90\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AE0\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B60\u0B61\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60\u0D61\u0D7A-\u0D7F\u0E01-\u0E2E\u0E30\u0E32\u0E33\u0E40-\u0E45\u10D0-\u10F0\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1E0D\u1E37\u1E41\u1E43\u1E45\u1E47\u1E6D\u1F00-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB2-\u1FB4\u1FB6\u1FB7\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD2\u1FD3\u1FD6\u1FD7\u1FE2-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CC9\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\u00AD\u200B-\u200D-]{${minWordLength},}`, "gui"
|
|
||||||
);
|
|
||||||
wordRegExpPool.set(key, reWord);
|
|
||||||
return reWord;
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hyphenate text according to setting in sel
|
|
||||||
* @param {string} text - the strint to be hyphenated
|
|
||||||
* @returns {string} hyphenated string according to setting of sel
|
|
||||||
*/
|
|
||||||
function hyphenateText(text) {
|
|
||||||
if (C.normalize) {
|
|
||||||
text = text.normalize("NFC");
|
|
||||||
}
|
|
||||||
let tn = text.replace(
|
|
||||||
regExpWord,
|
|
||||||
createWordHyphenator(lo, lang, sel)
|
|
||||||
);
|
|
||||||
if (selSettings.orphanControl !== 1) {
|
|
||||||
tn = tn.replace(
|
|
||||||
/(\u0020*)(\S+)(\s*)$/,
|
|
||||||
createOrphanController(sel)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return tn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hyphenate element according to setting in sel
|
|
||||||
* @param {object} el - the HTMLElement to be hyphenated
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
function hyphenateElement(el) {
|
|
||||||
event.fire(
|
|
||||||
"beforeElementHyphenation",
|
|
||||||
{
|
|
||||||
el,
|
|
||||||
lang
|
|
||||||
}
|
|
||||||
);
|
|
||||||
el.childNodes.forEach((n) => {
|
|
||||||
if (
|
|
||||||
n.nodeType === 3 &&
|
|
||||||
(/\S/).test(n.data) &&
|
|
||||||
n.data.length >= minWordLength
|
|
||||||
) {
|
|
||||||
n.data = hyphenateText(n.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
H.res.els.counter[0] -= 1;
|
|
||||||
event.fire(
|
|
||||||
"afterElementHyphenation",
|
|
||||||
{
|
|
||||||
el,
|
|
||||||
lang
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let r = null;
|
|
||||||
if (typeof entity === "string") {
|
|
||||||
r = hyphenateText(entity);
|
|
||||||
} else if (entity instanceof HTMLElement) {
|
|
||||||
hyphenateElement(entity);
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a language-specific string hyphenator
|
|
||||||
* @param {String} lang - The language this hyphenator hyphenates
|
|
||||||
*/
|
|
||||||
function createStringHyphenator(lang) {
|
|
||||||
return ((entity, sel = ".hyphenate") => {
|
|
||||||
if (typeof entity !== "string") {
|
|
||||||
event.fire(
|
|
||||||
"error",
|
|
||||||
Error("This use of hyphenators is deprecated. See https://mnater.github.io/Hyphenopoly/Hyphenators.html")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return hyphenate(lang, sel, entity);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a polyglot HTML hyphenator
|
|
||||||
*/
|
|
||||||
function createDOMHyphenator() {
|
|
||||||
return ((entity, sel = ".hyphenate") => {
|
|
||||||
collectElements(entity, sel).list.forEach((els, l) => {
|
|
||||||
els.forEach((elo) => {
|
|
||||||
hyphenate(l, elo.selector, elo.element);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
H.unhyphenate = () => {
|
|
||||||
H.res.els.list.forEach((els) => {
|
|
||||||
els.forEach((elo) => {
|
|
||||||
const n = elo.element.firstChild;
|
|
||||||
n.data = n.data.replace(RegExp(C[elo.selector].hyphen, "g"), "");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return Promise.resolve(H.res.els);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hyphenate all elements with a given language
|
|
||||||
* @param {string} lang The language
|
|
||||||
* @param {Array} elArr Array of elements
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
function hyphenateLangElements(lang, elements) {
|
|
||||||
const elArr = elements.list.get(lang);
|
|
||||||
if (elArr) {
|
|
||||||
elArr.forEach((elo) => {
|
|
||||||
hyphenate(lang, elo.selector, elo.element);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
event.fire(
|
|
||||||
"error",
|
|
||||||
Error(`Engine for language '${lang}' loaded, but no elements found.`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (elements.counter[0] === 0) {
|
|
||||||
w.clearTimeout(H.timeOutHandler);
|
|
||||||
H.hide(0, null);
|
|
||||||
event.fire(
|
|
||||||
"hyphenopolyEnd",
|
|
||||||
{
|
|
||||||
"msg": "hyphenopolyEnd"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (!C.keepAlive) {
|
|
||||||
window.Hyphenopoly = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert the exceptions from user input to Map
|
|
||||||
* @param {string} lang - The language for which the Map is created
|
|
||||||
* @return {Map}
|
|
||||||
*/
|
|
||||||
function createExceptionMap(lang) {
|
|
||||||
let exc = "";
|
|
||||||
if (C.exceptions.has(lang)) {
|
|
||||||
exc = C.exceptions.get(lang);
|
|
||||||
}
|
|
||||||
if (C.exceptions.has("global")) {
|
|
||||||
if (exc === "") {
|
|
||||||
exc = C.exceptions.get("global");
|
|
||||||
} else {
|
|
||||||
exc += ", " + C.exceptions.get("global");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (exc === "") {
|
|
||||||
return new Map();
|
|
||||||
}
|
|
||||||
return new Map(exc.split(", ").map((e) => {
|
|
||||||
return [e.replace(/-/g, ""), e];
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup lo
|
|
||||||
* @param {string} lang The language
|
|
||||||
* @param {function} hyphenateFunction The hyphenateFunction
|
|
||||||
* @param {string} alphabet List of used characters
|
|
||||||
* @param {number} leftmin leftmin
|
|
||||||
* @param {number} rightmin rightmin
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
function prepareLanguagesObj(
|
|
||||||
lang,
|
|
||||||
hyphenateFunction,
|
|
||||||
alphabet,
|
|
||||||
patternLeftmin,
|
|
||||||
patternRightmin
|
|
||||||
) {
|
|
||||||
C.selectors.forEach((sel) => {
|
|
||||||
const selSettings = C.get(sel);
|
|
||||||
if (selSettings.leftminPerLang === 0) {
|
|
||||||
selSettings.set("leftminPerLang", new Map());
|
|
||||||
}
|
|
||||||
if (selSettings.rightminPerLang === 0) {
|
|
||||||
selSettings.set("rightminPerLang", new Map());
|
|
||||||
}
|
|
||||||
selSettings.leftminPerLang.set(lang, Math.max(
|
|
||||||
patternLeftmin,
|
|
||||||
selSettings.leftmin,
|
|
||||||
Number(selSettings.leftminPerLang.get(lang)) || 0
|
|
||||||
));
|
|
||||||
|
|
||||||
selSettings.rightminPerLang.set(lang, Math.max(
|
|
||||||
patternRightmin,
|
|
||||||
selSettings.rightmin,
|
|
||||||
Number(selSettings.rightminPerLang.get(lang)) || 0
|
|
||||||
));
|
|
||||||
});
|
|
||||||
if (!H.languages) {
|
|
||||||
H.languages = new Map();
|
|
||||||
}
|
|
||||||
alphabet = alphabet.replace(/\\*-/g, "\\-");
|
|
||||||
H.languages.set(lang, {
|
|
||||||
alphabet,
|
|
||||||
"cache": new Map(),
|
|
||||||
"exc": createExceptionMap(lang),
|
|
||||||
"hyphenate": hyphenateFunction,
|
|
||||||
"ready": true,
|
|
||||||
"reNotAlphabet": RegExp(`[^${alphabet}]`, "i")
|
|
||||||
});
|
|
||||||
H.hy6ors.get(lang).resolve(createStringHyphenator(lang));
|
|
||||||
event.fire(
|
|
||||||
"engineReady",
|
|
||||||
{
|
|
||||||
lang
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (H.res.els) {
|
|
||||||
hyphenateLangElements(lang, H.res.els);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const decode = (() => {
|
|
||||||
const utf16ledecoder = new TextDecoder("utf-16le");
|
|
||||||
return ((ui16) => {
|
|
||||||
return utf16ledecoder.decode(ui16);
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup env for hyphenateFunction
|
|
||||||
* @param {ArrayBuffer} buf Memory buffer
|
|
||||||
* @param {function} hyphenateFunc hyphenateFunction
|
|
||||||
* @returns {function} hyphenateFunction with closured environment
|
|
||||||
*/
|
|
||||||
function encloseHyphenateFunction(buf, hyphenateFunc) {
|
|
||||||
const wordStore = new Uint16Array(buf, 0, 64);
|
|
||||||
return ((word, hyphencc, leftmin, rightmin) => {
|
|
||||||
wordStore.set([
|
|
||||||
...[...word].map((c) => {
|
|
||||||
return c.charCodeAt(0);
|
|
||||||
}),
|
|
||||||
0
|
|
||||||
]);
|
|
||||||
const len = hyphenateFunc(leftmin, rightmin, hyphencc);
|
|
||||||
if (len > 0) {
|
|
||||||
word = decode(
|
|
||||||
new Uint16Array(buf, 0, len)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return word;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate Wasm Engine
|
|
||||||
* @param {string} lang The language
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
function instantiateWasmEngine(heProm, lang) {
|
|
||||||
const wa = window.WebAssembly;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register character substitutions in the .wasm-hyphenEngine
|
|
||||||
* @param {number} alphalen - The length of the alphabet
|
|
||||||
* @param {object} exp - Export-object of the hyphenEngine
|
|
||||||
*/
|
|
||||||
function registerSubstitutions(alphalen, exp) {
|
|
||||||
if (C.substitute.has(lang)) {
|
|
||||||
const subst = C.substitute.get(lang);
|
|
||||||
subst.forEach((substituer, substituted) => {
|
|
||||||
const substitutedU = substituted.toUpperCase();
|
|
||||||
const substitutedUcc = (substitutedU === substituted)
|
|
||||||
? 0
|
|
||||||
: substitutedU.charCodeAt(0);
|
|
||||||
alphalen = exp.subst(
|
|
||||||
substituted.charCodeAt(0),
|
|
||||||
substitutedUcc,
|
|
||||||
substituer.charCodeAt(0)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return alphalen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate the hyphenEngine
|
|
||||||
* @param {object} res - The fetched ressource
|
|
||||||
*/
|
|
||||||
function handleWasm(res) {
|
|
||||||
const exp = res.instance.exports;
|
|
||||||
// eslint-disable-next-line multiline-ternary
|
|
||||||
let alphalen = (wa.Global) ? exp.lct.value : exp.lct;
|
|
||||||
alphalen = registerSubstitutions(alphalen, exp);
|
|
||||||
heProm.l.forEach((l) => {
|
|
||||||
prepareLanguagesObj(
|
|
||||||
l,
|
|
||||||
encloseHyphenateFunction(
|
|
||||||
exp.mem.buffer,
|
|
||||||
exp.hyphenate
|
|
||||||
),
|
|
||||||
decode(new Uint16Array(exp.mem.buffer, 1408, alphalen)),
|
|
||||||
/* eslint-disable multiline-ternary */
|
|
||||||
(wa.Global) ? exp.lmi.value : exp.lmi,
|
|
||||||
(wa.Global) ? exp.rmi.value : exp.rmi
|
|
||||||
/* eslint-enable multiline-ternary */
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
heProm.w.then((response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
if (
|
|
||||||
wa.instantiateStreaming &&
|
|
||||||
(response.headers.get("Content-Type") === "application/wasm")
|
|
||||||
) {
|
|
||||||
return wa.instantiateStreaming(response);
|
|
||||||
}
|
|
||||||
return response.arrayBuffer().then((ab) => {
|
|
||||||
return wa.instantiate(ab);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.reject(Error(`File ${lang}.wasm can't be loaded from ${H.paths.patterndir}`));
|
|
||||||
}).then(handleWasm, (e) => {
|
|
||||||
event.fire("error", e);
|
|
||||||
H.res.els.rem(lang);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
H.main = () => {
|
|
||||||
H.res.DOM.then(() => {
|
|
||||||
mainLanguage = getLang(w.document.documentElement, "", false);
|
|
||||||
if (!mainLanguage && C.defaultLanguage !== "") {
|
|
||||||
mainLanguage = C.defaultLanguage;
|
|
||||||
}
|
|
||||||
const elements = collectElements();
|
|
||||||
H.res.els = elements;
|
|
||||||
elements.list.forEach((ignore, lang) => {
|
|
||||||
if (H.languages &&
|
|
||||||
H.languages.has(lang) &&
|
|
||||||
H.languages.get(lang).ready
|
|
||||||
) {
|
|
||||||
hyphenateLangElements(lang, elements);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
H.res.he.forEach(instantiateWasmEngine);
|
|
||||||
|
|
||||||
Promise.all(
|
|
||||||
// Make sure all lang specific hyphenators and DOM are ready
|
|
||||||
[...H.hy6ors.entries()].
|
|
||||||
reduce((accumulator, value) => {
|
|
||||||
if (value[0] !== "HTML") {
|
|
||||||
return accumulator.concat(value[1]);
|
|
||||||
}
|
|
||||||
return accumulator;
|
|
||||||
}, []).
|
|
||||||
concat(H.res.DOM)
|
|
||||||
).then(() => {
|
|
||||||
H.hy6ors.get("HTML").resolve(createDOMHyphenator());
|
|
||||||
}, (e) => {
|
|
||||||
event.fire("error", e);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
H.main();
|
|
||||||
})(Hyphenopoly);
|
|
||||||
})(window, Object);
|
|
||||||
Vendored
-347
@@ -1,347 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license Hyphenopoly_Loader 5.2.0-beta.1 - client side hyphenation
|
|
||||||
* ©2023 Mathias Nater, Güttingen (mathiasnater at gmail dot com)
|
|
||||||
* https://github.com/mnater/Hyphenopoly
|
|
||||||
*
|
|
||||||
* Released under the MIT license
|
|
||||||
* http://mnater.github.io/Hyphenopoly/LICENSE
|
|
||||||
*/
|
|
||||||
/* globals Hyphenopoly:readonly */
|
|
||||||
window.Hyphenopoly = {};
|
|
||||||
|
|
||||||
((w, d, H, o) => {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for new Map
|
|
||||||
* @param {any} init - initialiser for new Map
|
|
||||||
* @returns {Map}
|
|
||||||
*/
|
|
||||||
const mp = (init) => {
|
|
||||||
return new Map(init);
|
|
||||||
};
|
|
||||||
|
|
||||||
const scriptName = "Hyphenopoly_Loader.js";
|
|
||||||
const thisScript = d.currentScript.src;
|
|
||||||
const store = sessionStorage;
|
|
||||||
let mainScriptLoaded = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main function runs the feature test and loads Hyphenopoly if
|
|
||||||
* necessary.
|
|
||||||
*/
|
|
||||||
const main = (() => {
|
|
||||||
const shortcuts = {
|
|
||||||
"ac": "appendChild",
|
|
||||||
"ce": "createElement",
|
|
||||||
"ct": "createTextNode"
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create deferred Promise
|
|
||||||
*
|
|
||||||
* From http://lea.verou.me/2016/12/resolve-promises-externally-with-
|
|
||||||
* this-one-weird-trick/
|
|
||||||
* @return {promise}
|
|
||||||
*/
|
|
||||||
const defProm = () => {
|
|
||||||
let res = null;
|
|
||||||
let rej = null;
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
|
||||||
res = resolve;
|
|
||||||
rej = reject;
|
|
||||||
});
|
|
||||||
promise.resolve = res;
|
|
||||||
promise.reject = rej;
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
H.ac = new AbortController();
|
|
||||||
const fetchOptions = {
|
|
||||||
"credentials": H.s.CORScredentials,
|
|
||||||
"signal": H.ac.signal
|
|
||||||
};
|
|
||||||
|
|
||||||
let stylesNode = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define function H.hide.
|
|
||||||
* This function hides (state = 1) or unhides (state = 0)
|
|
||||||
* the whole document (mode == 0) or
|
|
||||||
* each selected element (mode == 1) or
|
|
||||||
* text of each selected element (mode == 2) or
|
|
||||||
* nothing (mode == -1)
|
|
||||||
* @param {integer} state - State
|
|
||||||
* @param {integer} mode - Mode
|
|
||||||
*/
|
|
||||||
H.hide = (state, mode) => {
|
|
||||||
if (state) {
|
|
||||||
let vis = "{visibility:hidden!important}";
|
|
||||||
stylesNode = d[shortcuts.ce]("style");
|
|
||||||
let myStyle = "";
|
|
||||||
if (mode === 0) {
|
|
||||||
myStyle = "html" + vis;
|
|
||||||
} else if (mode !== -1) {
|
|
||||||
if (mode === 2) {
|
|
||||||
vis = "{color:transparent!important}";
|
|
||||||
}
|
|
||||||
o.keys(H.s.selectors).forEach((sel) => {
|
|
||||||
myStyle += sel + vis;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
stylesNode[shortcuts.ac](d[shortcuts.ct](myStyle));
|
|
||||||
d.head[shortcuts.ac](stylesNode);
|
|
||||||
} else if (stylesNode) {
|
|
||||||
stylesNode.remove();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const tester = (() => {
|
|
||||||
let fakeBody = null;
|
|
||||||
return {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append fakeBody with tests to document
|
|
||||||
* @returns {Object|null} The body element or null, if no tests
|
|
||||||
*/
|
|
||||||
"ap": () => {
|
|
||||||
if (fakeBody) {
|
|
||||||
d.documentElement[shortcuts.ac](fakeBody);
|
|
||||||
return fakeBody;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove fakeBody
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
"cl": () => {
|
|
||||||
if (fakeBody) {
|
|
||||||
fakeBody.remove();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and append div with CSS-hyphenated word
|
|
||||||
* @param {string} lang Language
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
"cr": (lang) => {
|
|
||||||
if (H.cf.langs.has(lang)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fakeBody = fakeBody || d[shortcuts.ce]("body");
|
|
||||||
const testDiv = d[shortcuts.ce]("div");
|
|
||||||
const ha = "hyphens:auto";
|
|
||||||
testDiv.lang = lang;
|
|
||||||
testDiv.style.cssText = `visibility:hidden;-webkit-${ha};-ms-${ha};${ha};width:48px;font-size:12px;line-height:12px;border:none;padding:0;word-wrap:normal`;
|
|
||||||
testDiv[shortcuts.ac](
|
|
||||||
d[shortcuts.ct](H.lrq.get(lang).wo.toLowerCase())
|
|
||||||
);
|
|
||||||
fakeBody[shortcuts.ac](testDiv);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if hyphens (ev.prefixed) is set to auto for the element.
|
|
||||||
* @param {Object} elm - the element
|
|
||||||
* @returns {Boolean} result of the check
|
|
||||||
*/
|
|
||||||
const checkCSSHyphensSupport = (elmStyle) => {
|
|
||||||
const h = elmStyle.hyphens ||
|
|
||||||
elmStyle.webkitHyphens ||
|
|
||||||
elmStyle.msHyphens;
|
|
||||||
return (h === "auto");
|
|
||||||
};
|
|
||||||
|
|
||||||
H.res = {
|
|
||||||
"he": mp()
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load hyphenEngines to H.res.he
|
|
||||||
*
|
|
||||||
* Make sure each .wasm is loaded exactly once, even for fallbacks
|
|
||||||
* Store a list of languages to by hyphenated with each .wasm
|
|
||||||
* @param {string} lang The language
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
const loadhyphenEngine = (lang) => {
|
|
||||||
const fn = H.lrq.get(lang).fn;
|
|
||||||
H.cf.pf = true;
|
|
||||||
H.cf.langs.set(lang, "H9Y");
|
|
||||||
if (H.res.he.has(fn)) {
|
|
||||||
H.res.he.get(fn).l.push(lang);
|
|
||||||
} else {
|
|
||||||
H.res.he.set(
|
|
||||||
fn,
|
|
||||||
{
|
|
||||||
"l": [lang],
|
|
||||||
"w": w.fetch(H.paths.patterndir + fn + ".wasm", fetchOptions)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
H.lrq.forEach((value, lang) => {
|
|
||||||
if (value.wo === "FORCEHYPHENOPOLY" || H.cf.langs.get(lang) === "H9Y") {
|
|
||||||
loadhyphenEngine(lang);
|
|
||||||
} else {
|
|
||||||
tester.cr(lang);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const testContainer = tester.ap();
|
|
||||||
if (testContainer) {
|
|
||||||
testContainer.querySelectorAll("div").forEach((n) => {
|
|
||||||
if (checkCSSHyphensSupport(n.style) && n.offsetHeight > 12) {
|
|
||||||
H.cf.langs.set(n.lang, "CSS");
|
|
||||||
} else {
|
|
||||||
loadhyphenEngine(n.lang);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tester.cl();
|
|
||||||
}
|
|
||||||
const hev = H.hev;
|
|
||||||
if (H.cf.pf) {
|
|
||||||
H.res.DOM = new Promise((res) => {
|
|
||||||
if (d.readyState === "loading") {
|
|
||||||
d.addEventListener(
|
|
||||||
"DOMContentLoaded",
|
|
||||||
res,
|
|
||||||
{
|
|
||||||
"once": true,
|
|
||||||
"passive": true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
res();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
H.hide(1, H.s.hide);
|
|
||||||
H.timeOutHandler = w.setTimeout(() => {
|
|
||||||
H.hide(0, null);
|
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
if (H.s.timeout & 1) {
|
|
||||||
H.ac.abort();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.info(scriptName + " timed out.");
|
|
||||||
}, H.s.timeout);
|
|
||||||
if (mainScriptLoaded) {
|
|
||||||
H.main();
|
|
||||||
} else {
|
|
||||||
// Load main script
|
|
||||||
fetch(H.paths.maindir + "Hyphenopoly.js", fetchOptions).
|
|
||||||
then((response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
response.blob().then((blb) => {
|
|
||||||
const script = d[shortcuts.ce]("script");
|
|
||||||
script.src = URL.createObjectURL(blb);
|
|
||||||
d.head[shortcuts.ac](script);
|
|
||||||
mainScriptLoaded = true;
|
|
||||||
URL.revokeObjectURL(script.src);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
H.hy6ors = mp();
|
|
||||||
H.cf.langs.forEach((langDef, lang) => {
|
|
||||||
if (langDef === "H9Y") {
|
|
||||||
H.hy6ors.set(lang, defProm());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
H.hy6ors.set("HTML", defProm());
|
|
||||||
H.hyphenators = new Proxy(H.hy6ors, {
|
|
||||||
"get": (target, key) => {
|
|
||||||
return target.get(key);
|
|
||||||
},
|
|
||||||
"set": () => {
|
|
||||||
// Inhibit setting of hyphenators
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
(() => {
|
|
||||||
if (hev && hev.polyfill) {
|
|
||||||
hev.polyfill();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
} else {
|
|
||||||
(() => {
|
|
||||||
if (hev && hev.tearDown) {
|
|
||||||
hev.tearDown();
|
|
||||||
}
|
|
||||||
w.Hyphenopoly = null;
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
(() => {
|
|
||||||
if (H.cft) {
|
|
||||||
store.setItem(scriptName, JSON.stringify(
|
|
||||||
{
|
|
||||||
"langs": [...H.cf.langs.entries()],
|
|
||||||
"pf": H.cf.pf
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
|
|
||||||
H.config = (c) => {
|
|
||||||
/**
|
|
||||||
* Sets default properties for an Object
|
|
||||||
* @param {object} obj - The object to set defaults to
|
|
||||||
* @param {object} defaults - The defaults to set
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
const setDefaults = (obj, defaults) => {
|
|
||||||
if (obj) {
|
|
||||||
o.entries(defaults).forEach(([k, v]) => {
|
|
||||||
// eslint-disable-next-line security/detect-object-injection
|
|
||||||
obj[k] = obj[k] || v;
|
|
||||||
});
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
return defaults;
|
|
||||||
};
|
|
||||||
|
|
||||||
H.cft = Boolean(c.cacheFeatureTests);
|
|
||||||
if (H.cft && store.getItem(scriptName)) {
|
|
||||||
H.cf = JSON.parse(store.getItem(scriptName));
|
|
||||||
H.cf.langs = mp(H.cf.langs);
|
|
||||||
} else {
|
|
||||||
H.cf = {
|
|
||||||
"langs": mp(),
|
|
||||||
"pf": false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const maindir = thisScript.slice(0, (thisScript.lastIndexOf("/") + 1));
|
|
||||||
const patterndir = maindir + "patterns/";
|
|
||||||
H.paths = setDefaults(c.paths, {
|
|
||||||
maindir,
|
|
||||||
patterndir
|
|
||||||
});
|
|
||||||
H.s = setDefaults(c.setup, {
|
|
||||||
"CORScredentials": "include",
|
|
||||||
"hide": "all",
|
|
||||||
"selectors": {".hyphenate": {}},
|
|
||||||
"timeout": 1000
|
|
||||||
});
|
|
||||||
// Change mode string to mode int
|
|
||||||
H.s.hide = ["all", "element", "text"].indexOf(H.s.hide);
|
|
||||||
if (c.handleEvent) {
|
|
||||||
H.hev = c.handleEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fallbacks = mp(o.entries(c.fallbacks || {}));
|
|
||||||
H.lrq = mp();
|
|
||||||
o.entries(c.require).forEach(([lang, wo]) => {
|
|
||||||
H.lrq.set(lang.toLowerCase(), {
|
|
||||||
"fn": fallbacks.get(lang) || lang,
|
|
||||||
wo
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
main();
|
|
||||||
};
|
|
||||||
})(window, document, Hyphenopoly, Object);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"path": "."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
CC0 1.0 Universal
|
|
||||||
==================
|
|
||||||
|
|
||||||
Statement of Purpose
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
|
|
||||||
|
|
||||||
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
|
|
||||||
|
|
||||||
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
|
|
||||||
|
|
||||||
1. Copyright and Related Rights.
|
|
||||||
--------------------------------
|
|
||||||
A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
|
|
||||||
|
|
||||||
i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
|
|
||||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
|
||||||
iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
|
|
||||||
iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
|
|
||||||
v. rights protecting the extraction, dissemination, use and reuse of data in a Work;
|
|
||||||
vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
|
|
||||||
vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
|
|
||||||
|
|
||||||
2. Waiver.
|
|
||||||
-----------
|
|
||||||
To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
|
|
||||||
|
|
||||||
3. Public License Fallback.
|
|
||||||
----------------------------
|
|
||||||
Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
|
|
||||||
|
|
||||||
4. Limitations and Disclaimers.
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
|
|
||||||
b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
|
|
||||||
c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
|
|
||||||
d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
Copyright 2017 The EB Garamond Project Authors (https://github.com/octaviopardo/EBGaramond12)
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,40 +0,0 @@
|
|||||||
# electron-quick-start
|
|
||||||
|
|
||||||
**Clone and run for a quick way to see Electron in action.**
|
|
||||||
|
|
||||||
This is a minimal Electron application based on the [Quick Start Guide](https://electronjs.org/docs/latest/tutorial/quick-start) within the Electron documentation.
|
|
||||||
|
|
||||||
A basic Electron application needs just these files:
|
|
||||||
|
|
||||||
- `package.json` - Points to the app's main file and lists its details and dependencies.
|
|
||||||
- `main.js` - Starts the app and creates a browser window to render HTML. This is the app's **main process**.
|
|
||||||
- `index.html` - A web page to render. This is the app's **renderer process**.
|
|
||||||
- `preload.js` - A content script that runs before the renderer process loads.
|
|
||||||
|
|
||||||
You can learn more about each of these components in depth within the [Tutorial](https://electronjs.org/docs/latest/tutorial/tutorial-prerequisites).
|
|
||||||
|
|
||||||
## To Use
|
|
||||||
|
|
||||||
To clone and run this repository you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone this repository
|
|
||||||
git clone https://github.com/electron/electron-quick-start
|
|
||||||
# Go into the repository
|
|
||||||
cd electron-quick-start
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
|
||||||
# Run the app
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: If you're using Linux Bash for Windows, [see this guide](https://www.howtogeek.com/261575/how-to-run-graphical-linux-desktop-applications-from-windows-10s-bash-shell/) or use `node` from the command prompt.
|
|
||||||
|
|
||||||
## Resources for Learning Electron
|
|
||||||
|
|
||||||
- [electronjs.org/docs](https://electronjs.org/docs) - all of Electron's documentation
|
|
||||||
- [Electron Fiddle](https://electronjs.org/fiddle) - Electron Fiddle, an app to test small Electron experiments
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[CC0 1.0 (Public Domain)](LICENSE.md)
|
|
||||||
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 MiB |
@@ -1,16 +0,0 @@
|
|||||||
# title: Das Herrenhaus
|
|
||||||
# author: Georg Tomitsch
|
|
||||||
|
|
||||||
VAR location = 0
|
|
||||||
|
|
||||||
INCLUDE Stats.ink
|
|
||||||
|
|
||||||
-> Herrenhaus
|
|
||||||
|
|
||||||
=== Herrenhaus ===
|
|
||||||
~ location = Herrenhaus
|
|
||||||
Du stehst vor einem Herrenhaus.
|
|
||||||
* Aktion 1 # 1
|
|
||||||
* Aktion 2 # 2
|
|
||||||
* Aktion 3 # 3
|
|
||||||
- -> END
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
// Quest tracking functions as described by Jon Ingold in his GDC 2017 talk.
|
|
||||||
|
|
||||||
// Every state implies the states before.
|
|
||||||
|
|
||||||
=== function state_reached(state) ===
|
|
||||||
~ return
|
|
||||||
|
|
||||||
// The state is never rolled back. If a lower or the same state is moved to nothing happens.
|
|
||||||
=== function move_to_state(state) ===
|
|
||||||
~ return
|
|
||||||
|
|
||||||
// Often the state is checked as a range (excluding first and last state)
|
|
||||||
=== function state_between(first_state, last_state) ===
|
|
||||||
~ return
|
|
||||||
|
|
||||||
// Implementation of ChoiceScript's Fairmath system
|
|
||||||
|
|
||||||
// Adjust the variable by adding amount percent of the current value
|
|
||||||
=== function set(ref variable, amount) ===
|
|
||||||
~ variable = MIN(100, variable + variable * amount / 100)
|
|
||||||
~ return variable
|
|
||||||
|
|
||||||
// Implementation of relative opposed pair stats
|
|
||||||
=== function opposed(positive, negative) ===
|
|
||||||
~ return positive / negative * 100 // TODO Check if this calculation is correct
|
|
||||||
|
|
||||||
|
|
||||||
// Inkle's default number writing function
|
|
||||||
=== function print_num(x)
|
|
||||||
{
|
|
||||||
- x >= 1000:
|
|
||||||
{print_num(x / 1000)} thousand { x mod 1000 > 0:{print_num(x mod 1000)}}
|
|
||||||
- x >= 100:
|
|
||||||
{print_num(x / 100)} hundred { x mod 100 > 0:and {print_num(x mod 100)}}
|
|
||||||
- x == 0:
|
|
||||||
zero
|
|
||||||
- else:
|
|
||||||
{ x >= 20:
|
|
||||||
{ x / 10:
|
|
||||||
- 2: twenty
|
|
||||||
- 3: thirty
|
|
||||||
- 4: forty
|
|
||||||
- 5: fifty
|
|
||||||
- 6: sixty
|
|
||||||
- 7: seventy
|
|
||||||
- 8: eighty
|
|
||||||
- 9: ninety
|
|
||||||
}
|
|
||||||
{ x mod 10 > 0:
|
|
||||||
<>-<>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ x < 10 || x > 20:
|
|
||||||
{ x mod 10:
|
|
||||||
- 1: one
|
|
||||||
- 2: two
|
|
||||||
- 3: three
|
|
||||||
- 4: four
|
|
||||||
- 5: five
|
|
||||||
- 6: six
|
|
||||||
- 7: seven
|
|
||||||
- 8: eight
|
|
||||||
- 9: nine
|
|
||||||
}
|
|
||||||
- else:
|
|
||||||
{ x:
|
|
||||||
- 10: ten
|
|
||||||
- 11: eleven
|
|
||||||
- 12: twelve
|
|
||||||
- 13: thirteen
|
|
||||||
- 14: fourteen
|
|
||||||
- 15: fifteen
|
|
||||||
- 16: sixteen
|
|
||||||
- 17: seventeen
|
|
||||||
- 18: eighteen
|
|
||||||
- 19: nineteen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-566
@@ -1,566 +0,0 @@
|
|||||||
(function(storyContent) {
|
|
||||||
// Create ink story from the content using inkjs
|
|
||||||
var story = new inkjs.Story(storyContent);
|
|
||||||
|
|
||||||
var savePoint = "";
|
|
||||||
|
|
||||||
let fade_in = true;
|
|
||||||
|
|
||||||
// Global tags - those at the top of the ink file
|
|
||||||
// We support:
|
|
||||||
// # theme: dark
|
|
||||||
// # author: Your Name
|
|
||||||
var globalTags = story.globalTags;
|
|
||||||
if( globalTags ) {
|
|
||||||
for(var i=0; i<story.globalTags.length; i++) {
|
|
||||||
var globalTag = story.globalTags[i];
|
|
||||||
var splitTag = splitPropertyTag(globalTag);
|
|
||||||
|
|
||||||
// THEME: dark
|
|
||||||
if( splitTag && splitTag.property == "title" ) {
|
|
||||||
var title = document.querySelector('.title');
|
|
||||||
title.innerHTML = splitTag.val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// author: Your Name
|
|
||||||
else if( splitTag && splitTag.property == "author" ) {
|
|
||||||
var byline = document.querySelector('.byline');
|
|
||||||
byline.innerHTML = "by "+splitTag.val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var storyContainer = document.querySelector('#story');
|
|
||||||
var choiceContainer = document.querySelector('#choices');
|
|
||||||
var outerScrollContainer = document.querySelector('#book');
|
|
||||||
|
|
||||||
function updateParagraphPreview(paragraph_data, indent_width, preview_width) {
|
|
||||||
var old_preview = document.getElementById("preview");
|
|
||||||
var preview = document.createElement("div");
|
|
||||||
preview.id = "preview";
|
|
||||||
preview.style.width = preview_width + 'px';
|
|
||||||
if(old_preview) {
|
|
||||||
old_preview.replaceWith(preview);
|
|
||||||
// preview = old_preview;
|
|
||||||
} else {
|
|
||||||
document.body.appendChild(preview);
|
|
||||||
}
|
|
||||||
// p = typesetParagraph(paragraph_data, indent_width);
|
|
||||||
// preview.appendChild(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeoutQueue = [];
|
|
||||||
|
|
||||||
function scheduleTimeout(func, delay, ...args) {
|
|
||||||
const timeoutObject = {
|
|
||||||
execute: () => func(...args),
|
|
||||||
timeoutId: null
|
|
||||||
};
|
|
||||||
|
|
||||||
timeoutObject.timeoutId = setTimeout(() => {
|
|
||||||
timeoutObject.execute();
|
|
||||||
timeoutQueue = timeoutQueue.filter(t => t !== timeoutObject);
|
|
||||||
}, delay);
|
|
||||||
|
|
||||||
timeoutQueue.push(timeoutObject);
|
|
||||||
|
|
||||||
return timeoutObject.timeoutId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fastForward() {
|
|
||||||
// Sort the queue based on timeoutId (assuming that smaller ids are scheduled earlier)
|
|
||||||
timeoutQueue.sort((a, b) => a.timeoutId - b.timeoutId);
|
|
||||||
// Clear and execute all timeouts
|
|
||||||
timeoutQueue.forEach(timeoutObject => {
|
|
||||||
clearTimeout(timeoutObject.timeoutId);
|
|
||||||
timeoutObject.execute();
|
|
||||||
});
|
|
||||||
|
|
||||||
timeoutQueue = [];
|
|
||||||
document.getElementById("page_right").scrollTo({top: document.getElementById("page_right").scrollHeight, behavior: 'smooth'});
|
|
||||||
}
|
|
||||||
|
|
||||||
// var numberOfPreviewLines = 0;
|
|
||||||
|
|
||||||
function typesetParagraph(paragraph_data, indent_width, delay = 0) {
|
|
||||||
console.log("Typesetting Paragraph with: ", paragraph_data, indent_width);
|
|
||||||
var left = indent_width;
|
|
||||||
var p = document.createElement("p");
|
|
||||||
p.style.position = 'relative';
|
|
||||||
var line_height = parseFloat(window.getComputedStyle(document.querySelector("#ruler")).lineHeight);
|
|
||||||
// numberOfPreviewLines += paragraph_data.breaks.length - 1;
|
|
||||||
// console.log("Calculated line height:", line_height);
|
|
||||||
p.style.height = line_height * (paragraph_data.breaks.length - 1) + 'px';
|
|
||||||
p.style.marginBlockEnd = 0;
|
|
||||||
for(let i = 1; i < paragraph_data.breaks.length; i++) {
|
|
||||||
if(i > 1)
|
|
||||||
left = 0;
|
|
||||||
for(let j = paragraph_data.breaks[i-1].position; j <= paragraph_data.breaks[i].position; j++) {
|
|
||||||
// console.log("i =",i,"j =",j,"from =",paragraph_data.breaks[i-1].position,"to =",paragraph_data.breaks[i].position,"node_width =", paragraph_data.nodes[j].width, "left =", left, "type =", paragraph_data.nodes[j].type, "value =", paragraph_data.nodes[j].value);
|
|
||||||
if(paragraph_data.nodes[j].type === 'box' && paragraph_data.nodes[j].value !== '' && j < paragraph_data.breaks[i].position) {
|
|
||||||
if(j > paragraph_data.breaks[i-1].position + 1 && paragraph_data.nodes[j-1].type === 'penalty' && p.lastChild) {
|
|
||||||
p.lastChild.textContent += paragraph_data.nodes[j].value;
|
|
||||||
left += paragraph_data.nodes[j].width;
|
|
||||||
} else {
|
|
||||||
let word = document.createElement("span");
|
|
||||||
word.style.position = 'absolute';
|
|
||||||
word.classList.add("fade-in");
|
|
||||||
word.style.top = line_height * (i - 1) + 'px';
|
|
||||||
word.style.left = left + 'px';
|
|
||||||
word.innerHTML = paragraph_data.nodes[j].value;
|
|
||||||
insertAfter(delay, p, word);
|
|
||||||
delay += 100.0;
|
|
||||||
// p.appendChild(word);
|
|
||||||
if(j > 0)
|
|
||||||
left += paragraph_data.nodes[j].width;
|
|
||||||
else
|
|
||||||
left += paragraph_data.nodes[j].width - indent_width;
|
|
||||||
}
|
|
||||||
} else if(j > paragraph_data.breaks[i-1].position && paragraph_data.nodes[j].type === 'glue' && paragraph_data.nodes[j].width !== 0 && j <= paragraph_data.breaks[i].position) {
|
|
||||||
// Insert space character
|
|
||||||
if(paragraph_data.breaks[i].ratio > 0) {
|
|
||||||
left += paragraph_data.nodes[j].width + paragraph_data.breaks[i].ratio * paragraph_data.nodes[j].stretch;
|
|
||||||
} else {
|
|
||||||
left += paragraph_data.nodes[j].width + paragraph_data.breaks[i].ratio * paragraph_data.nodes[j].shrink;
|
|
||||||
}
|
|
||||||
} else if(paragraph_data.nodes[j].type === 'penalty' && paragraph_data.nodes[j].penalty === 100 && j === paragraph_data.breaks[i].position) {
|
|
||||||
let word = document.createElement("span");
|
|
||||||
word.style.position = 'absolute';
|
|
||||||
word.style.top = line_height * (i - 1) + 'px';
|
|
||||||
word.style.left = left + 'px';
|
|
||||||
word.innerHTML = "-";
|
|
||||||
insertAfter(delay, p, word);
|
|
||||||
delay += 100;
|
|
||||||
// p.appendChild(word);
|
|
||||||
// left += paragraph_data.nodes[j].width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return [p, delay];
|
|
||||||
}
|
|
||||||
|
|
||||||
function measureText(str) {
|
|
||||||
if (str === ' ') {
|
|
||||||
str = '\u00A0';
|
|
||||||
}
|
|
||||||
ruler.textContent = str;
|
|
||||||
return ruler.getClientRects()[0].width;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBookDimensions() {
|
|
||||||
const vw = window.innerWidth;
|
|
||||||
const vh = window.innerHeight;
|
|
||||||
const viewportAspectRatio = vw / vh;
|
|
||||||
const imageAspectRatio = 2727 / 1691;
|
|
||||||
|
|
||||||
let bookWidth, bookHeight;
|
|
||||||
|
|
||||||
if (viewportAspectRatio > imageAspectRatio) {
|
|
||||||
bookWidth = vh * imageAspectRatio;
|
|
||||||
bookHeight = vh;
|
|
||||||
} else {
|
|
||||||
bookWidth = vw;
|
|
||||||
bookHeight = vw / imageAspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.documentElement.style.setProperty('--book-width', `${bookWidth}px`);
|
|
||||||
document.documentElement.style.setProperty('--book-height', `${bookHeight}px`);
|
|
||||||
|
|
||||||
// Setting a CSS variable that will be either vw or vh depending on the viewport aspect ratio
|
|
||||||
document.documentElement.style.setProperty(
|
|
||||||
"--viewport-dimension",
|
|
||||||
viewportAspectRatio > imageAspectRatio ? 'vw' : 'vh'
|
|
||||||
);
|
|
||||||
document.documentElement.style.setProperty('--viewport-aspect-ratio', viewportAspectRatio);
|
|
||||||
let story = document.getElementById("story");
|
|
||||||
let paddingTop = window.getComputedStyle(story).paddingTop;
|
|
||||||
let paddingBottom = window.getComputedStyle(story).paddingBottom;
|
|
||||||
document.documentElement.style.setProperty('--story-line-height', (story.clientHeight - paddingTop - paddingBottom) / 28)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the aspect ratio when the page loads
|
|
||||||
updateBookDimensions();
|
|
||||||
|
|
||||||
// Update the aspect ratio whenever the window is resized
|
|
||||||
window.addEventListener('resize', updateBookDimensions);
|
|
||||||
|
|
||||||
window.addEventListener('keydown', (event) => {
|
|
||||||
if (event.code === 'Space') {
|
|
||||||
fade_in = false;
|
|
||||||
fastForward();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// page features setup
|
|
||||||
var hasSave = loadSavePoint();
|
|
||||||
setupButtons(hasSave);
|
|
||||||
|
|
||||||
// Set initial save point
|
|
||||||
savePoint = story.state.toJson();
|
|
||||||
|
|
||||||
// Kick off the start of the story!
|
|
||||||
continueStory(true);
|
|
||||||
|
|
||||||
// Main story processing function. Each time this is called it generates
|
|
||||||
// all the next content up as far as the next set of choices.
|
|
||||||
function continueStory(firstTime) {
|
|
||||||
|
|
||||||
var paragraphIndex = 0;
|
|
||||||
var delay = 0.0;
|
|
||||||
|
|
||||||
// Don't over-scroll past new content
|
|
||||||
var previousBottomEdge = firstTime ? 0 : contentBottomEdgeY();
|
|
||||||
|
|
||||||
var fade_in = true
|
|
||||||
|
|
||||||
// Generate story text - loop through available content
|
|
||||||
while(story.canContinue) {
|
|
||||||
// Get ink to generate the next paragraph
|
|
||||||
var paragraphText = story.Continue();
|
|
||||||
var tags = story.currentTags;
|
|
||||||
|
|
||||||
// Any special tags included with this line
|
|
||||||
var customClasses = [];
|
|
||||||
for(var i=0; i<tags.length; i++) {
|
|
||||||
var tag = tags[i];
|
|
||||||
|
|
||||||
// Detect tags of the form "X: Y". Currently used for IMAGE and CLASS but could be
|
|
||||||
// customised to be used for other things too.
|
|
||||||
var splitTag = splitPropertyTag(tag);
|
|
||||||
|
|
||||||
// AUDIO: src
|
|
||||||
if( splitTag && splitTag.property == "AUDIO" ) {
|
|
||||||
if('audio' in this) {
|
|
||||||
this.audio.pause();
|
|
||||||
this.audio.removeAttribute('src');
|
|
||||||
this.audio.load();
|
|
||||||
}
|
|
||||||
this.audio = new Audio(splitTag.val);
|
|
||||||
this.audio.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
// AUDIOLOOP: src
|
|
||||||
else if( splitTag && splitTag.property == "AUDIOLOOP" ) {
|
|
||||||
if('audioLoop' in this) {
|
|
||||||
this.audioLoop.pause();
|
|
||||||
this.audioLoop.removeAttribute('src');
|
|
||||||
this.audioLoop.load();
|
|
||||||
}
|
|
||||||
this.audioLoop = new Audio(splitTag.val);
|
|
||||||
this.audioLoop.play();
|
|
||||||
this.audioLoop.loop = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IMAGE: src
|
|
||||||
if( splitTag && splitTag.property == "IMAGE" ) {
|
|
||||||
var imageElement = document.createElement('img');
|
|
||||||
imageElement.src = splitTag.val;
|
|
||||||
storyContainer.appendChild(imageElement);
|
|
||||||
|
|
||||||
showAfter(delay, imageElement);
|
|
||||||
delay += 200.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINK: url
|
|
||||||
else if( splitTag && splitTag.property == "LINK" ) {
|
|
||||||
window.location.href = splitTag.val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINKOPEN: url
|
|
||||||
else if( splitTag && splitTag.property == "LINKOPEN" ) {
|
|
||||||
window.open(splitTag.val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BACKGROUND: src
|
|
||||||
else if( splitTag && splitTag.property == "BACKGROUND" ) {
|
|
||||||
outerScrollContainer.style.backgroundImage = 'url('+splitTag.val+')';
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLASS: className
|
|
||||||
else if( splitTag && splitTag.property == "CLASS" ) {
|
|
||||||
customClasses.push(splitTag.val);
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLEAR - removes all existing content.
|
|
||||||
// RESTART - clears everything and restarts the story from the beginning
|
|
||||||
else if( tag == "CLEAR" || tag == "RESTART" ) {
|
|
||||||
removeAll("p");
|
|
||||||
removeAll("img");
|
|
||||||
|
|
||||||
// Comment out this line if you want to leave the header visible when clearing
|
|
||||||
// setVisible(".header", false);
|
|
||||||
|
|
||||||
if( tag == "RESTART" ) {
|
|
||||||
restart();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create paragraph element (initially hidden)
|
|
||||||
(function(text) {
|
|
||||||
if(text.trim().length === 0)
|
|
||||||
return;
|
|
||||||
console.log("Hyphenating:", text);
|
|
||||||
let hyphenator_promise = Hyphenopoly.hyphenators["en-us"].then((hyphenator_en) => {
|
|
||||||
var measure = parseFloat(window.getComputedStyle(document.getElementById("story")).width);
|
|
||||||
var indentWidth = parseFloat(window.getComputedStyle(document.querySelector("#indent")).textIndent);
|
|
||||||
var previewWidth = measure;
|
|
||||||
var preview_data = kap(hyphenator_en(text, '.hyphenatePipe'), measureText, 'align-justify', measure, true, indentWidth);
|
|
||||||
return { preview_data, indentWidth, previewWidth};
|
|
||||||
});
|
|
||||||
hyphenator_promise.then(({ preview_data, indentWidth, previewWidth }) => {
|
|
||||||
// updateParagraphPreview(preview_data, indentWidth, previewWidth);
|
|
||||||
var p, d;
|
|
||||||
[p, d] = typesetParagraph(preview_data, indentWidth, delay);
|
|
||||||
delay = d;
|
|
||||||
// Add any custom classes derived from ink tags
|
|
||||||
for(var i=0; i<customClasses.length; i++)
|
|
||||||
p.classList.add(customClasses[i]);
|
|
||||||
storyContainer.appendChild(p);
|
|
||||||
p.scrollIntoView({ behavior: 'smooth'});
|
|
||||||
});
|
|
||||||
})(paragraphText);
|
|
||||||
|
|
||||||
// var paragraphElement = document.createElement('p');
|
|
||||||
// var words = paragraphText.split(" ");
|
|
||||||
// words.forEach(word => {
|
|
||||||
// var wordElement = document.createElement('span');
|
|
||||||
// Hyphenopoly.hyphenators["en-us"].then((hyphenator_en) => {
|
|
||||||
// wordElement.innerHTML = hyphenator_en(word);
|
|
||||||
// });
|
|
||||||
// // showAfter(delay, wordElement);
|
|
||||||
// insertAfter(delay, paragraphElement, wordElement, fade_in);
|
|
||||||
// insertAfter(delay, paragraphElement, document.createTextNode(" "), false);
|
|
||||||
// delay +=100.0;
|
|
||||||
// // paragraphElement.appendChild(wordElement);
|
|
||||||
// // paragraphElement.appendChild(document.createTextNode(" "));
|
|
||||||
// });
|
|
||||||
// // paragraphElement.innerHTML = paragraphText;
|
|
||||||
// storyContainer.appendChild(paragraphElement);
|
|
||||||
|
|
||||||
|
|
||||||
// Fade in paragraph after a short delay
|
|
||||||
// showAfter(delay, paragraphElement);
|
|
||||||
// delay += 200.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create HTML choices from ink choices
|
|
||||||
story.currentChoices.forEach(function(choice) {
|
|
||||||
|
|
||||||
// Create paragraph with anchor element
|
|
||||||
var choiceParagraphElement = document.createElement('p');
|
|
||||||
choiceParagraphElement.classList.add("choice");
|
|
||||||
choiceParagraphElement.innerHTML = `<a href='#'>${choice.text}</a>`
|
|
||||||
// choiceContainer.appendChild(choiceParagraphElement);
|
|
||||||
insertAfter(delay, choiceContainer, choiceParagraphElement, fade_in);
|
|
||||||
// Fade choice in after a short delay
|
|
||||||
// showAfter(delay, choiceParagraphElement);
|
|
||||||
delay += 200.0;
|
|
||||||
|
|
||||||
// Click on choice
|
|
||||||
var choiceAnchorEl = choiceParagraphElement.querySelectorAll("a")[0];
|
|
||||||
choiceAnchorEl.addEventListener("click", function(event) {
|
|
||||||
|
|
||||||
// Don't follow <a> link
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Remove all existing choices
|
|
||||||
removeAll(".choice", true);
|
|
||||||
|
|
||||||
// Tell the story where to go next
|
|
||||||
story.ChooseChoiceIndex(choice.index);
|
|
||||||
|
|
||||||
// This is where the save button will save from
|
|
||||||
savePoint = story.state.toJson();
|
|
||||||
|
|
||||||
// Aaand loop
|
|
||||||
continueStory();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extend height to fit
|
|
||||||
// We do this manually so that removing elements and creating new ones doesn't
|
|
||||||
// cause the height (and therefore scroll) to jump backwards temporarily.
|
|
||||||
// storyContainer.style.height = contentBottomEdgeY()+"px";
|
|
||||||
|
|
||||||
if( !firstTime )
|
|
||||||
scrollDown(previousBottomEdge);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function restart() {
|
|
||||||
story.ResetState();
|
|
||||||
|
|
||||||
setVisible(".header", true);
|
|
||||||
removeAll(".choice", true);
|
|
||||||
|
|
||||||
// set save point to here
|
|
||||||
savePoint = story.state.toJson();
|
|
||||||
|
|
||||||
continueStory(true);
|
|
||||||
|
|
||||||
outerScrollContainer.scrollTo({ top: 0, left: 0, behavior: 'smooth'});
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------
|
|
||||||
// Various Helper functions
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
// Fades in an element after a specified delay
|
|
||||||
function showAfter(delay, el) {
|
|
||||||
el.classList.add("hide");
|
|
||||||
setTimeout(function() {
|
|
||||||
setTimeout(function() { el.classList.remove("hide") }, delay);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertAfter(delay, target, el, fade_in = true) {
|
|
||||||
if(fade_in) {
|
|
||||||
el.classList.add("fade-in");
|
|
||||||
scheduleTimeout(function() {
|
|
||||||
target.appendChild(el);
|
|
||||||
el.scrollIntoView({ behavior: 'smooth'});
|
|
||||||
}, delay);
|
|
||||||
} else {
|
|
||||||
scheduleTimeout(function() {
|
|
||||||
target.appendChild(el);
|
|
||||||
}, delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scrolls the page down, but no further than the bottom edge of what you could
|
|
||||||
// see previously, so it doesn't go too far.
|
|
||||||
function scrollDown(previousBottomEdge) {
|
|
||||||
return; // TODO: Fix or remove function
|
|
||||||
// Line up top of screen with the bottom of where the previous content ended
|
|
||||||
var target = previousBottomEdge;
|
|
||||||
|
|
||||||
// Can't go further than the very bottom of the page
|
|
||||||
var limit = outerScrollContainer.scrollHeight - outerScrollContainer.clientHeight;
|
|
||||||
if( target > limit ) target = limit;
|
|
||||||
|
|
||||||
var start = outerScrollContainer.scrollTop;
|
|
||||||
|
|
||||||
var dist = target - start;
|
|
||||||
var duration = 300 + 300*dist/100;
|
|
||||||
var startTime = null;
|
|
||||||
function step(time) {
|
|
||||||
if( startTime == null ) startTime = time;
|
|
||||||
var t = (time-startTime) / duration;
|
|
||||||
var lerp = 3*t*t - 2*t*t*t; // ease in/out
|
|
||||||
outerScrollContainer.scrollTo({ left: 0, top: (1.0-lerp)*start + lerp*target, behavior: 'smooth'});
|
|
||||||
if( t < 1 ) requestAnimationFrame(step);
|
|
||||||
}
|
|
||||||
requestAnimationFrame(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Y coordinate of the bottom end of all the story content, used
|
|
||||||
// for growing the container, and deciding how far to scroll.
|
|
||||||
function contentBottomEdgeY() {
|
|
||||||
var bottomElement = storyContainer.lastElementChild;
|
|
||||||
return bottomElement ? bottomElement.offsetTop + bottomElement.offsetHeight : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all elements that match the given selector. Used for removing choices after
|
|
||||||
// you've picked one, as well as for the CLEAR and RESTART tags.
|
|
||||||
function removeAll(selector, choices = false)
|
|
||||||
{
|
|
||||||
if(choices)
|
|
||||||
var allElements = choiceContainer.querySelectorAll(selector);
|
|
||||||
else
|
|
||||||
var allElements = storyContainer.querySelectorAll(selector);
|
|
||||||
for(var i=0; i<allElements.length; i++) {
|
|
||||||
var el = allElements[i];
|
|
||||||
el.parentNode.removeChild(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used for hiding and showing the header when you CLEAR or RESTART the story respectively.
|
|
||||||
function setVisible(selector, visible)
|
|
||||||
{
|
|
||||||
var allElements = storyContainer.querySelectorAll(selector);
|
|
||||||
for(var i=0; i<allElements.length; i++) {
|
|
||||||
var el = allElements[i];
|
|
||||||
if( !visible )
|
|
||||||
el.classList.add("invisible");
|
|
||||||
else
|
|
||||||
el.classList.remove("invisible");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for parsing out tags of the form:
|
|
||||||
// # PROPERTY: value
|
|
||||||
// e.g. IMAGE: source path
|
|
||||||
function splitPropertyTag(tag) {
|
|
||||||
var propertySplitIdx = tag.indexOf(":");
|
|
||||||
if( propertySplitIdx != null ) {
|
|
||||||
var property = tag.substr(0, propertySplitIdx).trim();
|
|
||||||
var val = tag.substr(propertySplitIdx+1).trim();
|
|
||||||
return {
|
|
||||||
property: property,
|
|
||||||
val: val
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loads save state if exists in the browser memory
|
|
||||||
function loadSavePoint() {
|
|
||||||
try {
|
|
||||||
let savedState = window.localStorage.getItem('save-state');
|
|
||||||
if (savedState) {
|
|
||||||
story.state.LoadJson(savedState);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.debug("Couldn't load save state");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to hook up the functionality for global functionality buttons
|
|
||||||
function setupButtons(hasSave) {
|
|
||||||
|
|
||||||
let rewindEl = document.getElementById("rewind");
|
|
||||||
if (rewindEl) rewindEl.addEventListener("click", function(event) {
|
|
||||||
removeAll("p");
|
|
||||||
removeAll("img");
|
|
||||||
setVisible(".header", false);
|
|
||||||
restart();
|
|
||||||
});
|
|
||||||
|
|
||||||
let saveEl = document.getElementById("save");
|
|
||||||
if (saveEl) saveEl.addEventListener("click", function(event) {
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem('save-state', savePoint);
|
|
||||||
document.getElementById("reload").removeAttribute("disabled");
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Couldn't save state");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
let reloadEl = document.getElementById("reload");
|
|
||||||
if (!hasSave) {
|
|
||||||
reloadEl.setAttribute("disabled", "disabled");
|
|
||||||
}
|
|
||||||
reloadEl.addEventListener("click", function(event) {
|
|
||||||
if (reloadEl.getAttribute("disabled"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
removeAll("p");
|
|
||||||
removeAll("img");
|
|
||||||
removeAll(".choice", true);
|
|
||||||
try {
|
|
||||||
let savedState = window.localStorage.getItem('save-state');
|
|
||||||
if (savedState) story.state.LoadJson(savedState);
|
|
||||||
} catch (e) {
|
|
||||||
console.debug("Couldn't load save state");
|
|
||||||
}
|
|
||||||
continueStory(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
})(storyContent);
|
|
||||||
Vendored
-1034
File diff suppressed because it is too large
Load Diff
@@ -1,57 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
|
||||||
<!-- meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'blob'; style-src 'self' 'unsafe-inline'" -->
|
|
||||||
<title>Ink.js Book Runtime</title>
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p id="versions">We are using Node.js <span id="node-version"></span>,
|
|
||||||
Chromium <span id="chrome-version"></span>,
|
|
||||||
and Electron <span id="electron-version"></span>.</p>
|
|
||||||
<div id="book">
|
|
||||||
<div id="page_left">
|
|
||||||
<div class="header">
|
|
||||||
<h2 class="byline l10n-by">by </h2>
|
|
||||||
<h1 class="title"></h1>
|
|
||||||
<h3 class="subtitle"></h3>
|
|
||||||
<div class="separator"><double>❦</double></div>
|
|
||||||
</div>
|
|
||||||
<div id="controls" class="buttons">
|
|
||||||
<a class="l10n-speech" id="speech" title="Toggle text to speech" disabled="disabled">speech</a>
|
|
||||||
<span><a id="speed_reset"><span class="l10n-speed">speed<sup>*</sup></span></a><input type="range" min="0" max="100" value="50" id="speed" name="speed" /></span>
|
|
||||||
<a class="l10n-restart" id="rewind" title="Restart story from beginning" disabled="disabled">restart</a>
|
|
||||||
<a class="l10n-save" id="save" title="Save progress">save</a>
|
|
||||||
<a class="l10n-load" id="reload" title="Reload from save point" disabled="disabled">load</a>
|
|
||||||
</div>
|
|
||||||
<div id="choices" class="container">
|
|
||||||
</div>
|
|
||||||
<div class="l10n-remark" id="remark"><i><sup>*</sup>click on page or press spacebar to fast forward text animation</i></div>
|
|
||||||
</div>
|
|
||||||
<div id="page_right">
|
|
||||||
<div id="story" class="container">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="ruler"></div>
|
|
||||||
<div class="l10n-prompt" id="indent">What do you want to do next?</div>
|
|
||||||
<div id="lighting" />
|
|
||||||
|
|
||||||
<!-- You can also require other files to run in this process -->
|
|
||||||
<script src="smartypants.js"></script>
|
|
||||||
<script src="linked-list.js"></script>
|
|
||||||
<script src="linebreak.js"></script>
|
|
||||||
<script src="knuth-and-plass.js"></script>
|
|
||||||
<script src="ink-full.js"></script>
|
|
||||||
<!-- <script src="TheIntercept.js"></script> -->
|
|
||||||
<script src="Hyphenopoly_Loader.js"></script>
|
|
||||||
<script>
|
|
||||||
var locale = "de";
|
|
||||||
</script>
|
|
||||||
<script src="game.js"></script>
|
|
||||||
<script src="./renderer.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Vendored
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
-2
File diff suppressed because one or more lines are too long
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"path": "."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"CodeGPT.apiKey": "Ollama"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("path"),t=require("fs");function r(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(r){if("default"!==r){var n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:function(){return e[r]}})}})),t.default=e,Object.freeze(t)}var n=r(e),o=r(t);function i(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}function a(e,t,r){return t&&i(e.prototype,t),r&&i(e,r),Object.defineProperty(e,"prototype",{writable:!1}),e}var u=a((function e(t){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.rootPath=t,this.ResolveInkFilename=function(e){if(void 0!==r.rootPath&&""!==r.rootPath)return n.join(r.rootPath,e);var t=process.cwd();return n.join(t,e)},this.LoadInkFileContents=function(e){return o.readFileSync(e,"utf-8")}}));exports.PosixFileHandler=u;
|
|
||||||
Vendored
-56
@@ -1,56 +0,0 @@
|
|||||||
function kap(text, measureText, measure, hyphenation) {
|
|
||||||
console.log("Typesetting hyphenated text:", text, measure);
|
|
||||||
if (!hyphenation) {
|
|
||||||
text = text.replace(/\|/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
let hyphenWidth = measureText('-');
|
|
||||||
let spaceWidth = measureText('\u00A0');
|
|
||||||
let nodes = [];
|
|
||||||
|
|
||||||
text.split(/([.,:;!?] |\s|\||<.*?>)/u).forEach(function (fragment) {
|
|
||||||
let fragmentWidth = measureText(fragment);
|
|
||||||
|
|
||||||
if (fragment === ' ') {
|
|
||||||
let stretch = (spaceWidth * 3) / 6;
|
|
||||||
let shrink = (spaceWidth * 3) / 9;
|
|
||||||
|
|
||||||
nodes.push(linebreak.glue(spaceWidth, stretch, shrink));
|
|
||||||
} else if (fragment === '|') {
|
|
||||||
// nodes.push(linebreak.penalty(hyphenWidth, 100, 1));
|
|
||||||
nodes.push(linebreak.penalty(hyphenWidth * 0.25, 100, 1));
|
|
||||||
} else if (fragment.match(/(<.*?>)/u)) {
|
|
||||||
nodes.push(linebreak.tag(fragmentWidth, fragment));
|
|
||||||
} else if (fragment.match(/[.,:;!?] /u)) {
|
|
||||||
let punctuation = fragment.match(/([.,:;!?])( )/u);
|
|
||||||
let punctuationSymbolWidth = measureText(punctuation[1]) * 0.25;
|
|
||||||
let punctuationWidth = measureText(punctuation[1]) * 0.75 + spaceWidth;
|
|
||||||
nodes.push(linebreak.box(punctuationSymbolWidth, punctuation[1]));
|
|
||||||
let stretch = (punctuationWidth * 3) / 6;
|
|
||||||
let shrink = (punctuationWidth * 3) / 9;
|
|
||||||
|
|
||||||
nodes.push(linebreak.glue(punctuationWidth, stretch, shrink));
|
|
||||||
} else if (fragment.match(/(\s+)/u)) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
nodes.push(linebreak.box(fragmentWidth, fragment));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
nodes.push(linebreak.glue(0, linebreak.infinity, 0));
|
|
||||||
nodes.push(linebreak.penalty(0, -linebreak.infinity, 1));
|
|
||||||
|
|
||||||
let demerits = {
|
|
||||||
line: 10,
|
|
||||||
flagged: 100,
|
|
||||||
fitness: 3000
|
|
||||||
};
|
|
||||||
|
|
||||||
let breaks = linebreak(nodes, measure, { tolerance: 3, demerits });
|
|
||||||
|
|
||||||
if (!breaks.length) {
|
|
||||||
breaks = linebreak(nodes, measure, { tolerance: 10, demerits });
|
|
||||||
}
|
|
||||||
|
|
||||||
return { nodes, breaks };
|
|
||||||
}
|
|
||||||
Vendored
-334
@@ -1,334 +0,0 @@
|
|||||||
var linebreak = function (nodes, lines, settings = {
|
|
||||||
demerits: {
|
|
||||||
line: 10,
|
|
||||||
flagged: 100,
|
|
||||||
fitness: 3000
|
|
||||||
},
|
|
||||||
tolerance: 2
|
|
||||||
}) {
|
|
||||||
const options = settings;
|
|
||||||
activeNodes = new LinkedList(),
|
|
||||||
sum = {
|
|
||||||
width: 0,
|
|
||||||
stretch: 0,
|
|
||||||
shrink: 0
|
|
||||||
},
|
|
||||||
lineLengths = lines,
|
|
||||||
breaks = [],
|
|
||||||
tmp = {
|
|
||||||
data: {
|
|
||||||
demerits: Infinity
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function breakpoint(position, demerits, ratio, line, fitnessClass, totals, previous) {
|
|
||||||
return {
|
|
||||||
position: position,
|
|
||||||
demerits: demerits,
|
|
||||||
ratio: ratio,
|
|
||||||
line: line,
|
|
||||||
fitnessClass: fitnessClass,
|
|
||||||
totals: totals || {
|
|
||||||
width: 0,
|
|
||||||
stretch: 0,
|
|
||||||
shrink: 0
|
|
||||||
},
|
|
||||||
previous: previous
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeCost(start, end, active, currentLine) {
|
|
||||||
var width = sum.width - active.totals.width,
|
|
||||||
stretch = 0,
|
|
||||||
shrink = 0,
|
|
||||||
// If the current line index is within the list of linelengths, use it, otherwise use
|
|
||||||
// the last line length of the list.
|
|
||||||
lineLength = currentLine < lineLengths.length ? lineLengths[currentLine - 1] : lineLengths[lineLengths.length - 1];
|
|
||||||
|
|
||||||
if (nodes[end].type === 'penalty') {
|
|
||||||
width += nodes[end].width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width < lineLength) {
|
|
||||||
// Calculate the stretch ratio
|
|
||||||
stretch = sum.stretch - active.totals.stretch;
|
|
||||||
|
|
||||||
if (stretch > 0) {
|
|
||||||
return (lineLength - width) / stretch;
|
|
||||||
} else {
|
|
||||||
return linebreak.infinity;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (width > lineLength) {
|
|
||||||
// Calculate the shrink ratio
|
|
||||||
shrink = sum.shrink - active.totals.shrink;
|
|
||||||
|
|
||||||
if (shrink > 0) {
|
|
||||||
return (lineLength - width) / shrink;
|
|
||||||
} else {
|
|
||||||
return linebreak.infinity;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// perfect match
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Add width, stretch and shrink values from the current
|
|
||||||
// break point up to the next box or forced penalty.
|
|
||||||
function computeSum(breakPointIndex) {
|
|
||||||
var result = {
|
|
||||||
width: sum.width,
|
|
||||||
stretch: sum.stretch,
|
|
||||||
shrink: sum.shrink
|
|
||||||
},
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
for (i = breakPointIndex; i < nodes.length; i += 1) {
|
|
||||||
if (nodes[i].type === 'glue') {
|
|
||||||
result.width += nodes[i].width;
|
|
||||||
result.stretch += nodes[i].stretch;
|
|
||||||
result.shrink += nodes[i].shrink;
|
|
||||||
} else if (nodes[i].type === 'box' || (nodes[i].type === 'penalty' && nodes[i].penalty === -linebreak.infinity && i > breakPointIndex)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
let graphNodes = [];
|
|
||||||
let graphEdges = [];
|
|
||||||
|
|
||||||
// The main loop of the algorithm
|
|
||||||
function mainLoop(node, index, nodes) {
|
|
||||||
var active = activeNodes.first,
|
|
||||||
next = null,
|
|
||||||
ratio = 0,
|
|
||||||
demerits = 0,
|
|
||||||
candidates = [],
|
|
||||||
badness,
|
|
||||||
currentLine = 0,
|
|
||||||
tmpSum,
|
|
||||||
currentClass = 0,
|
|
||||||
fitnessClass,
|
|
||||||
candidate,
|
|
||||||
newNode;
|
|
||||||
|
|
||||||
// The inner loop iterates through all the active nodes with line < currentLine and then
|
|
||||||
// breaks out to insert the new active node candidates before looking at the next active
|
|
||||||
// nodes for the next lines. The result of this is that the active node list is always
|
|
||||||
// sorted by line number.
|
|
||||||
while (active !== null) {
|
|
||||||
|
|
||||||
candidates = [{
|
|
||||||
demerits: Infinity
|
|
||||||
}, {
|
|
||||||
demerits: Infinity
|
|
||||||
}, {
|
|
||||||
demerits: Infinity
|
|
||||||
}, {
|
|
||||||
demerits: Infinity
|
|
||||||
}];
|
|
||||||
|
|
||||||
// Iterate through the linked list of active nodes to find new potential active nodes
|
|
||||||
// and deactivate current active nodes.
|
|
||||||
while (active !== null) {
|
|
||||||
next = active.next;
|
|
||||||
currentLine = active.data.line + 1;
|
|
||||||
ratio = computeCost(active.data.position, index, active.data, currentLine);
|
|
||||||
|
|
||||||
// Deactive nodes when the distance between the current active node and the
|
|
||||||
// current node becomes too large (i.e. it exceeds the stretch limit and the stretch
|
|
||||||
// ratio becomes negative) or when the current node is a forced break (i.e. the end
|
|
||||||
// of the paragraph when we want to remove all active nodes, but possibly have a final
|
|
||||||
// candidate active node---if the paragraph can be set using the given tolerance value.)
|
|
||||||
if (ratio < -1 || (node.type === 'penalty' && node.penalty === -linebreak.infinity)) {
|
|
||||||
activeNodes.remove(active);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the ratio is within the valid range of -1 <= ratio <= tolerance calculate the
|
|
||||||
// total demerits and record a candidate active node.
|
|
||||||
if (-1 <= ratio && ratio <= options.tolerance) {
|
|
||||||
badness = 100 * Math.pow(Math.abs(ratio), 3);
|
|
||||||
|
|
||||||
// Positive penalty
|
|
||||||
if (node.type === 'penalty' && node.penalty >= 0) {
|
|
||||||
demerits = Math.pow(options.demerits.line + badness, 2) + Math.pow(node.penalty, 2);
|
|
||||||
// Negative penalty but not a forced break
|
|
||||||
} else if (node.type === 'penalty' && node.penalty !== -linebreak.infinity) {
|
|
||||||
demerits = Math.pow(options.demerits.line + badness, 2) - Math.pow(node.penalty, 2);
|
|
||||||
// All other cases
|
|
||||||
} else {
|
|
||||||
demerits = Math.pow(options.demerits.line + badness, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.type === 'penalty' && nodes[active.data.position].type === 'penalty') {
|
|
||||||
demerits += options.demerits.flagged * node.flagged * nodes[active.data.position].flagged;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the fitness class for this candidate active node.
|
|
||||||
if (ratio < -0.5) {
|
|
||||||
currentClass = 0;
|
|
||||||
} else if (ratio <= 0.5) {
|
|
||||||
currentClass = 1;
|
|
||||||
} else if (ratio <= 1) {
|
|
||||||
currentClass = 2;
|
|
||||||
} else {
|
|
||||||
currentClass = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a fitness penalty to the demerits if the fitness classes of two adjacent lines
|
|
||||||
// differ too much.
|
|
||||||
if (Math.abs(currentClass - active.data.fitnessClass) > 1) {
|
|
||||||
demerits += options.demerits.fitness;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the total demerits of the active node to get the total demerits of this candidate node.
|
|
||||||
demerits += active.data.demerits;
|
|
||||||
|
|
||||||
// Only store the best candidate for each fitness class
|
|
||||||
if (demerits < candidates[currentClass].demerits) {
|
|
||||||
candidates[currentClass] = {
|
|
||||||
active: active,
|
|
||||||
demerits: demerits,
|
|
||||||
ratio: ratio
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
active = next;
|
|
||||||
|
|
||||||
// Stop iterating through active nodes to insert new candidate active nodes in the active list
|
|
||||||
// before moving on to the active nodes for the next line.
|
|
||||||
// TODO: The Knuth and Plass paper suggests a conditional for currentLine < j0. This means paragraphs
|
|
||||||
// with identical line lengths will not be sorted by line number. Find out if that is a desirable outcome.
|
|
||||||
// For now I left this out, as it only adds minimal overhead to the algorithm and keeping the active node
|
|
||||||
// list sorted has a higher priority.
|
|
||||||
if (active !== null && active.data.line >= currentLine) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpSum = computeSum(index);
|
|
||||||
|
|
||||||
for (fitnessClass = 0; fitnessClass < candidates.length; fitnessClass += 1) {
|
|
||||||
candidate = candidates[fitnessClass];
|
|
||||||
|
|
||||||
if (candidate.demerits < Infinity) {
|
|
||||||
newNode = new Node(breakpoint(index, candidate.demerits, candidate.ratio,
|
|
||||||
candidate.active.data.line + 1, fitnessClass, tmpSum, candidate.active));
|
|
||||||
|
|
||||||
graphNodes.push({
|
|
||||||
id: index
|
|
||||||
});
|
|
||||||
|
|
||||||
graphEdges.push({
|
|
||||||
from: index,
|
|
||||||
to: candidate.active.data.position,
|
|
||||||
label: candidate.ratio.toFixed(2)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (active !== null) {
|
|
||||||
activeNodes.insertBefore(active, newNode);
|
|
||||||
} else {
|
|
||||||
activeNodes.push(newNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an active node for the start of the paragraph.
|
|
||||||
activeNodes.push(new Node(breakpoint(0, 0, 0, 0, 0, undefined, null)));
|
|
||||||
|
|
||||||
graphNodes.push({
|
|
||||||
id: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
nodes.forEach(function (node, index, nodes) {
|
|
||||||
if (node.type === 'box') {
|
|
||||||
sum.width += node.width;
|
|
||||||
} else if (node.type === 'glue') {
|
|
||||||
if (index > 0 && nodes[index - 1].type === 'box') {
|
|
||||||
mainLoop(node, index, nodes);
|
|
||||||
}
|
|
||||||
sum.width += node.width;
|
|
||||||
sum.stretch += node.stretch;
|
|
||||||
sum.shrink += node.shrink;
|
|
||||||
} else if (node.type === 'penalty' && node.penalty !== linebreak.infinity) {
|
|
||||||
mainLoop(node, index, nodes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if (activeNodes.size !== 0) {
|
|
||||||
// Find the best active node (the one with the least total demerits.)
|
|
||||||
activeNodes.forEach(function (node) {
|
|
||||||
if (node.data.demerits < tmp.data.demerits) {
|
|
||||||
tmp = node;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
graphNodes.forEach(function (n) {
|
|
||||||
let label = nodes[n.id].value;
|
|
||||||
|
|
||||||
if (nodes[n.id].type === 'glue') {
|
|
||||||
label = nodes[n.id - 1].value;
|
|
||||||
} else if (nodes[n.id].type === 'penalty') {
|
|
||||||
label = nodes[n.id - 1].value;
|
|
||||||
} else {
|
|
||||||
label = nodes[n.id].value;
|
|
||||||
}
|
|
||||||
n.label = label;
|
|
||||||
});
|
|
||||||
|
|
||||||
while (tmp !== null) {
|
|
||||||
breaks.push({
|
|
||||||
position: tmp.data.position,
|
|
||||||
ratio: tmp.data.ratio
|
|
||||||
});
|
|
||||||
tmp = tmp.data.previous;
|
|
||||||
}
|
|
||||||
return breaks.reverse();
|
|
||||||
} else {
|
|
||||||
console.warn('Overfull paragraph.');
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
linebreak.infinity = 10000;
|
|
||||||
|
|
||||||
linebreak.glue = function (width, stretch, shrink) {
|
|
||||||
return {
|
|
||||||
type: 'glue',
|
|
||||||
width: width,
|
|
||||||
stretch: stretch,
|
|
||||||
shrink: shrink
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
linebreak.box = function (width, value) {
|
|
||||||
return {
|
|
||||||
type: 'box',
|
|
||||||
width: width,
|
|
||||||
value: value
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
linebreak.tag = function (width, value) {
|
|
||||||
return {
|
|
||||||
type: 'tag',
|
|
||||||
width: width,
|
|
||||||
value: value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
linebreak.penalty = function (width, penalty, flagged) {
|
|
||||||
return {
|
|
||||||
type: 'penalty',
|
|
||||||
width: width,
|
|
||||||
penalty: penalty,
|
|
||||||
flagged: flagged
|
|
||||||
};
|
|
||||||
};
|
|
||||||
Vendored
-187
@@ -1,187 +0,0 @@
|
|||||||
class LinkedList {
|
|
||||||
constructor() {
|
|
||||||
this.head = null;
|
|
||||||
this.tail = null;
|
|
||||||
this.listSize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
return this.listSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLinked(node) {
|
|
||||||
return !((node && node.prev === null && node.next === null && this.tail !== node && this.head !== node) || this.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
isEmpty() {
|
|
||||||
return this.listSize === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get first() {
|
|
||||||
return this.head;
|
|
||||||
}
|
|
||||||
|
|
||||||
get last() {
|
|
||||||
return this.last;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return this.toArray().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray() {
|
|
||||||
var node = this.head,
|
|
||||||
result = [];
|
|
||||||
while (node !== null) {
|
|
||||||
result.push(node);
|
|
||||||
node = node.next;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that modifying the list during
|
|
||||||
// iteration is not safe.
|
|
||||||
forEach(fun) {
|
|
||||||
var node = this.head;
|
|
||||||
while (node !== null) {
|
|
||||||
fun(node);
|
|
||||||
node = node.next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contains(n) {
|
|
||||||
var node = this.head;
|
|
||||||
if (!this.isLinked(n)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
while (node !== null) {
|
|
||||||
if (node === n) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
node = node.next;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
at(i) {
|
|
||||||
var node = this.head, index = 0;
|
|
||||||
|
|
||||||
if (i >= this.listLength || i < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (node !== null) {
|
|
||||||
if (i === index) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
node = node.next;
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertAfter(node, newNode) {
|
|
||||||
if (!this.isLinked(node)) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
newNode.prev = node;
|
|
||||||
newNode.next = node.next;
|
|
||||||
if (node.next === null) {
|
|
||||||
this.tail = newNode;
|
|
||||||
} else {
|
|
||||||
node.next.prev = newNode;
|
|
||||||
}
|
|
||||||
node.next = newNode;
|
|
||||||
this.listSize += 1;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertBefore(node, newNode) {
|
|
||||||
if (!this.isLinked(node)) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
newNode.prev = node.prev;
|
|
||||||
newNode.next = node;
|
|
||||||
if (node.prev === null) {
|
|
||||||
this.head = newNode;
|
|
||||||
} else {
|
|
||||||
node.prev.next = newNode;
|
|
||||||
}
|
|
||||||
node.prev = newNode;
|
|
||||||
this.listSize += 1;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
push(node) {
|
|
||||||
if (this.head === null) {
|
|
||||||
this.unshift(node);
|
|
||||||
} else {
|
|
||||||
this.insertAfter(this.tail, node);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
unshift(node) {
|
|
||||||
if (this.head === null) {
|
|
||||||
this.head = node;
|
|
||||||
this.tail = node;
|
|
||||||
node.prev = null;
|
|
||||||
node.next = null;
|
|
||||||
this.listSize += 1;
|
|
||||||
} else {
|
|
||||||
this.insertBefore(this.head, node);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(node) {
|
|
||||||
if (!this.isLinked(node)) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
if (node.prev === null) {
|
|
||||||
this.head = node.next;
|
|
||||||
} else {
|
|
||||||
node.prev.next = node.next;
|
|
||||||
}
|
|
||||||
if (node.next === null) {
|
|
||||||
this.tail = node.prev;
|
|
||||||
} else {
|
|
||||||
node.next.prev = node.prev;
|
|
||||||
}
|
|
||||||
this.listSize -= 1;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
pop() {
|
|
||||||
var node = this.tail;
|
|
||||||
this.tail.prev.next = null;
|
|
||||||
this.tail = this.tail.prev;
|
|
||||||
this.listSize -= 1;
|
|
||||||
node.prev = null;
|
|
||||||
node.next = null;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
shift() {
|
|
||||||
var node = this.head;
|
|
||||||
this.head.next.prev = null;
|
|
||||||
this.head = this.head.next;
|
|
||||||
this.listSize -= 1;
|
|
||||||
node.prev = null;
|
|
||||||
node.next = null;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Node {
|
|
||||||
constructor(data) {
|
|
||||||
this.prev = null;
|
|
||||||
this.next = null;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return this.data.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-108
@@ -1,108 +0,0 @@
|
|||||||
// Modules to control application life and create native browser window
|
|
||||||
const { contextBridge, ipcMain, session, app, BrowserWindow } = require('electron')
|
|
||||||
const path = require('path')
|
|
||||||
const fs = require('fs');
|
|
||||||
const vm = require('vm');
|
|
||||||
require('./speech');
|
|
||||||
|
|
||||||
// const fetch = require('node-fetch');
|
|
||||||
|
|
||||||
// // Use a polyfill for fetch
|
|
||||||
// if (!globalThis.fetch) {
|
|
||||||
// globalThis.fetch = fetch;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const hyphenopolyScript = fs.readFileSync(require.resolve('./Hyphenopoly_Loader.js'), 'utf-8');
|
|
||||||
// vm.runInThisContext(hyphenopolyScript, { filename: 'Hyphenopoly_Loader.js' });
|
|
||||||
|
|
||||||
// Hyphenopoly.config({
|
|
||||||
// require: {
|
|
||||||
// 'en-us': 'FORCEHYPHENOPOLY',
|
|
||||||
// 'de': 'Silbentrennungsalgorithmus',
|
|
||||||
// },
|
|
||||||
// paths: {
|
|
||||||
// maindir: './',
|
|
||||||
// patterndir: './patterns/',
|
|
||||||
// },
|
|
||||||
// setup: {
|
|
||||||
// selectors: {
|
|
||||||
// '.hyphenate': {
|
|
||||||
// hyphen: '­',
|
|
||||||
// },
|
|
||||||
// '.hyphenatePipe': {
|
|
||||||
// hyphen: '|',
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// contextBridge.exposeInMainWorld('api', {
|
|
||||||
// hyphenateWord: async (word, selector = '.hyphenate') => {
|
|
||||||
// const hyphenator = await Hyphenopoly.hyphenators['en-us'];
|
|
||||||
// return hyphenator(word, selector);
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
const debug = true;
|
|
||||||
|
|
||||||
function createWindow () {
|
|
||||||
// Create the browser window.
|
|
||||||
const mainWindow = new BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
// fullscreen: true,
|
|
||||||
// frame: false,
|
|
||||||
// titleBarStyle: 'hidden',
|
|
||||||
autoHideMenuBar: true,
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: false,
|
|
||||||
contextIsolation: true,
|
|
||||||
enableRemoteModule: false,
|
|
||||||
// contentSecurityPolicy: "script-src 'self' 'unsafe-inline';",
|
|
||||||
preload: path.join(__dirname, 'preload.js')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if(!debug)
|
|
||||||
mainWindow.removeMenu();
|
|
||||||
|
|
||||||
// and load the index.html of the app.
|
|
||||||
mainWindow.loadFile('index.html')
|
|
||||||
|
|
||||||
mainWindow.maximize()
|
|
||||||
// Open the DevTools.
|
|
||||||
// mainWindow.webContents.openDevTools()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
|
||||||
// initialization and is ready to create browser windows.
|
|
||||||
// Some APIs can only be used after this event occurs.
|
|
||||||
app.whenReady().then(() => {
|
|
||||||
|
|
||||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
|
||||||
callback({
|
|
||||||
responseHeaders: {
|
|
||||||
...details.responseHeaders,
|
|
||||||
'Content-Security-Policy': ['default-src \'self\'; script-src \'self\' \'unsafe-inline\' \'unsafe-eval\' blob:; style-src \'self\' \'unsafe-inline\'']
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
createWindow()
|
|
||||||
|
|
||||||
app.on('activate', function () {
|
|
||||||
// On macOS it's common to re-create a window in the app when the
|
|
||||||
// dock icon is clicked and there are no other windows open.
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
|
||||||
// for applications and their menu bar to stay active until the user quits
|
|
||||||
// explicitly with Cmd + Q.
|
|
||||||
app.on('window-all-closed', function () {
|
|
||||||
if (process.platform !== 'darwin') app.quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
|
||||||
// code. You can also put them in separate files and require them here.
|
|
||||||
Generated
-1754
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "electron-quick-start",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "A minimal Electron application",
|
|
||||||
"main": "main.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "electron ."
|
|
||||||
},
|
|
||||||
"repository": "https://github.com/electron/electron-quick-start",
|
|
||||||
"keywords": [
|
|
||||||
"Electron",
|
|
||||||
"quick",
|
|
||||||
"start",
|
|
||||||
"tutorial",
|
|
||||||
"demo"
|
|
||||||
],
|
|
||||||
"author": "GitHub",
|
|
||||||
"license": "CC0-1.0",
|
|
||||||
"devDependencies": {
|
|
||||||
"electron": "^25.1.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "^1.4.0",
|
|
||||||
"crypto": "^1.0.1",
|
|
||||||
"fs": "^0.0.1-security",
|
|
||||||
"node-fetch": "^3.3.1",
|
|
||||||
"play-sound": "^1.1.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user