diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-07-17 17:10:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-17 17:10:25 -0400 |
commit | fafa746a632b1907d9cca262f689d7bec4e0f940 (patch) | |
tree | f328ef581376cbda0b9d99bd9c3f7892de50a3c1 /ext/js/dom/sandbox/css-style-applier.js | |
parent | 74381302c7a658dc3fd5e6c3a76842dd430341bb (diff) |
Sandbox script folders (#1837)
* Move scripts
* Update paths
* Fix ordering
* Simplify eslint rules
Diffstat (limited to 'ext/js/dom/sandbox/css-style-applier.js')
-rw-r--r-- | ext/js/dom/sandbox/css-style-applier.js | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/ext/js/dom/sandbox/css-style-applier.js b/ext/js/dom/sandbox/css-style-applier.js new file mode 100644 index 00000000..c617fead --- /dev/null +++ b/ext/js/dom/sandbox/css-style-applier.js @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/** + * This class is used to apply CSS styles to elements using a consistent method + * that is the same across different browsers. + */ +class CssStyleApplier { + /** + * Creates a new instance of the class. + * @param styleDataUrl The local URL to the JSON file continaing the style rules. + * The style rules should be of the format: + * [ + * { + * selectors: [(selector:string)...], + * styles: [ + * [(property:string), (value:string)]... + * ] + * }... + * ] + */ + constructor(styleDataUrl) { + this._styleDataUrl = styleDataUrl; + this._styleData = []; + this._cachedRules = new Map(); + } + + /** + * Loads the data file for use. + */ + async prepare() { + this._styleData = await this._fetchJsonAsset(this._styleDataUrl); + } + + /** + * Applies CSS styles directly to the "style" attribute using the "class" attribute. + * This only works for elements with a single class. + * @param elements An iterable collection of HTMLElement objects. + */ + applyClassStyles(elements) { + const elementStyles = []; + for (const element of elements) { + const {className} = element; + if (className.length === 0) { continue; } + let cssTextNew = ''; + for (const {selectorText, styles} of this._getRulesForClass(className)) { + if (!element.matches(selectorText)) { continue; } + cssTextNew += this._getCssText(styles); + } + cssTextNew += element.style.cssText; + elementStyles.push({element, style: cssTextNew}); + } + for (const {element, style} of elementStyles) { + element.removeAttribute('class'); + if (style.length > 0) { + element.setAttribute('style', style); + } else { + element.removeAttribute('style'); + } + } + } + + // Private + + async _fetchJsonAsset(url) { + const response = await fetch(url, { + method: 'GET', + mode: 'no-cors', + cache: 'default', + credentials: 'omit', + redirect: 'follow', + referrerPolicy: 'no-referrer' + }); + if (!response.ok) { + throw new Error(`Failed to fetch ${url}: ${response.status}`); + } + return await response.json(); + } + + _getRulesForClass(className) { + let rules = this._cachedRules.get(className); + if (typeof rules !== 'undefined') { return rules; } + + rules = []; + this._cachedRules.set(className, rules); + + const classNamePattern = new RegExp(`.${className}(?![0-9a-zA-Z-])`, ''); + for (const {selectors, styles} of this._styleData) { + const selectorText = selectors.join(','); + if (!classNamePattern.test(selectorText)) { continue; } + rules.push({selectorText, styles}); + } + + return rules; + } + + _getCssText(styles) { + let cssText = ''; + for (const [property, value] of styles) { + cssText += `${property}:${value};`; + } + return cssText; + } +} |