Last active
August 24, 2022 13:29
-
-
Save blaadje/d5c054b85033a7c96ec80dfc7c0d47a1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Prismic formatting | |
// @namespace http://tampermonkey.net/ | |
// @version 0.2 | |
// @description try to take over the world! | |
// @author blaadje | |
// @match https://japhy.prismic.io/* | |
// @icon https://www.google.com/s2/favicons?domain=github.com | |
// @grant none | |
// ==/UserScript== | |
(function () { | |
"use strict"; | |
var ARG_OPN = "{"; | |
var ARG_CLS = "}"; | |
var ARG_SEP = ","; | |
var NUM_ARG = "#"; | |
var TAG_OPN = "<"; | |
var TAG_CLS = ">"; | |
var TAG_END = "</"; | |
var TAG_SELF_CLS = "/>"; | |
var ESC = "'"; | |
var OFFSET = "offset:"; | |
var simpleTypes = [ | |
"number", | |
"date", | |
"time", | |
"ordinal", | |
"duration", | |
"spellout", | |
]; | |
var submTypes = ["plural", "select", "selectordinal"]; | |
function parse( | |
pattern /*: string */, | |
options /*:: ?: { tagsType?: string, tokens?: Token[] } */ | |
) /*: AST */ { | |
return parseAST( | |
{ | |
pattern: String(pattern), | |
index: 0, | |
tagsType: (options && options.tagsType) || null, | |
tokens: (options && options.tokens) || null, | |
}, | |
"" | |
); | |
} | |
function parseAST( | |
current /*: Context */, | |
parentType /*: string */ | |
) /*: AST */ { | |
var pattern = current.pattern; | |
var length = pattern.length; | |
var elements /*: AST */ = []; | |
var start = current.index; | |
var text = parseText(current, parentType); | |
if (text) elements.push(text); | |
if (text && current.tokens) | |
current.tokens.push(["text", pattern.slice(start, current.index)]); | |
while (current.index < length) { | |
if (pattern[current.index] === ARG_CLS) { | |
if (!parentType) throw expected(current); | |
break; | |
} | |
if ( | |
parentType && | |
current.tagsType && | |
pattern.slice(current.index, current.index + TAG_END.length) === TAG_END | |
) | |
break; | |
elements.push(parsePlaceholder(current)); | |
start = current.index; | |
text = parseText(current, parentType); | |
if (text) elements.push(text); | |
if (text && current.tokens) | |
current.tokens.push(["text", pattern.slice(start, current.index)]); | |
} | |
return elements; | |
} | |
function parseText( | |
current /*: Context */, | |
parentType /*: string */ | |
) /*: string */ { | |
var pattern = current.pattern; | |
var length = pattern.length; | |
var isHashSpecial = | |
parentType === "plural" || parentType === "selectordinal"; | |
var isAngleSpecial = !!current.tagsType; | |
var isArgStyle = parentType === "{style}"; | |
var text = ""; | |
while (current.index < length) { | |
var char = pattern[current.index]; | |
if ( | |
char === ARG_OPN || | |
char === ARG_CLS || | |
(isHashSpecial && char === NUM_ARG) || | |
(isAngleSpecial && char === TAG_OPN) || | |
(isArgStyle && isWhitespace(char.charCodeAt(0))) | |
) { | |
break; | |
} else if (char === ESC) { | |
char = pattern[++current.index]; | |
if (char === ESC) { | |
// double is always 1 ' | |
text += char; | |
++current.index; | |
} else if ( | |
// only when necessary | |
char === ARG_OPN || | |
char === ARG_CLS || | |
(isHashSpecial && char === NUM_ARG) || | |
(isAngleSpecial && char === TAG_OPN) || | |
isArgStyle | |
) { | |
text += char; | |
while (++current.index < length) { | |
char = pattern[current.index]; | |
if (char === ESC && pattern[current.index + 1] === ESC) { | |
// double is always 1 ' | |
text += ESC; | |
++current.index; | |
} else if (char === ESC) { | |
// end of quoted | |
++current.index; | |
break; | |
} else { | |
text += char; | |
} | |
} | |
} else { | |
// lone ' is just a ' | |
text += ESC; | |
// already incremented | |
} | |
} else { | |
text += char; | |
++current.index; | |
} | |
} | |
return text; | |
} | |
function isWhitespace(code /*: number */) /*: boolean */ { | |
return ( | |
(code >= 0x09 && code <= 0x0d) || | |
code === 0x20 || | |
code === 0x85 || | |
code === 0xa0 || | |
code === 0x180e || | |
(code >= 0x2000 && code <= 0x200d) || | |
code === 0x2028 || | |
code === 0x2029 || | |
code === 0x202f || | |
code === 0x205f || | |
code === 0x2060 || | |
code === 0x3000 || | |
code === 0xfeff | |
); | |
} | |
function skipWhitespace(current /*: Context */) /*: void */ { | |
var pattern = current.pattern; | |
var length = pattern.length; | |
var start = current.index; | |
while ( | |
current.index < length && | |
isWhitespace(pattern.charCodeAt(current.index)) | |
) { | |
++current.index; | |
} | |
if (start < current.index && current.tokens) { | |
current.tokens.push([ | |
"space", | |
current.pattern.slice(start, current.index), | |
]); | |
} | |
} | |
function parsePlaceholder(current /*: Context */) /*: Placeholder */ { | |
var pattern = current.pattern; | |
if (pattern[current.index] === NUM_ARG) { | |
if (current.tokens) current.tokens.push(["syntax", NUM_ARG]); | |
++current.index; // move passed # | |
return [NUM_ARG]; | |
} | |
var tag = parseTag(current); | |
if (tag) return tag; | |
/* istanbul ignore if should be unreachable if parseAST and parseText are right */ | |
if (pattern[current.index] !== ARG_OPN) throw expected(current, ARG_OPN); | |
if (current.tokens) current.tokens.push(["syntax", ARG_OPN]); | |
++current.index; // move passed { | |
skipWhitespace(current); | |
var id = parseId(current); | |
if (!id) throw expected(current, "placeholder id"); | |
if (current.tokens) current.tokens.push(["id", id]); | |
skipWhitespace(current); | |
var char = pattern[current.index]; | |
if (char === ARG_CLS) { | |
// end placeholder | |
if (current.tokens) current.tokens.push(["syntax", ARG_CLS]); | |
++current.index; // move passed } | |
return [id]; | |
} | |
if (char !== ARG_SEP) throw expected(current, ARG_SEP + " or " + ARG_CLS); | |
if (current.tokens) current.tokens.push(["syntax", ARG_SEP]); | |
++current.index; // move passed , | |
skipWhitespace(current); | |
var type = parseId(current); | |
if (!type) throw expected(current, "placeholder type"); | |
if (current.tokens) current.tokens.push(["type", type]); | |
skipWhitespace(current); | |
char = pattern[current.index]; | |
if (char === ARG_CLS) { | |
// end placeholder | |
if (current.tokens) current.tokens.push(["syntax", ARG_CLS]); | |
if (type === "plural" || type === "selectordinal" || type === "select") { | |
throw expected(current, type + " sub-messages"); | |
} | |
++current.index; // move passed } | |
return [id, type]; | |
} | |
if (char !== ARG_SEP) throw expected(current, ARG_SEP + " or " + ARG_CLS); | |
if (current.tokens) current.tokens.push(["syntax", ARG_SEP]); | |
++current.index; // move passed , | |
skipWhitespace(current); | |
var arg; | |
if (type === "plural" || type === "selectordinal") { | |
var offset = parsePluralOffset(current); | |
skipWhitespace(current); | |
arg = [id, type, offset, parseSubMessages(current, type)]; | |
} else if (type === "select") { | |
arg = [id, type, parseSubMessages(current, type)]; | |
} else if (simpleTypes.indexOf(type) >= 0) { | |
arg = [id, type, parseSimpleFormat(current)]; | |
} else { | |
// custom placeholder type | |
var index = current.index; | |
var format /*: string | SubMessages */ = parseSimpleFormat(current); | |
skipWhitespace(current); | |
if (pattern[current.index] === ARG_OPN) { | |
current.index = index; // rewind, since should have been submessages | |
format = parseSubMessages(current, type); | |
} | |
arg = [id, type, format]; | |
} | |
skipWhitespace(current); | |
if (pattern[current.index] !== ARG_CLS) throw expected(current, ARG_CLS); | |
if (current.tokens) current.tokens.push(["syntax", ARG_CLS]); | |
++current.index; // move passed } | |
return arg; | |
} | |
function parseTag(current /*: Context */) /*: ?Placeholder */ { | |
var tagsType = current.tagsType; | |
if (!tagsType || current.pattern[current.index] !== TAG_OPN) return; | |
if ( | |
current.pattern.slice(current.index, current.index + TAG_END.length) === | |
TAG_END | |
) { | |
throw expected(current, null, "closing tag without matching opening tag"); | |
} | |
if (current.tokens) current.tokens.push(["syntax", TAG_OPN]); | |
++current.index; // move passed < | |
var id = parseId(current, true); | |
if (!id) throw expected(current, "placeholder id"); | |
if (current.tokens) current.tokens.push(["id", id]); | |
skipWhitespace(current); | |
if ( | |
current.pattern.slice( | |
current.index, | |
current.index + TAG_SELF_CLS.length | |
) === TAG_SELF_CLS | |
) { | |
if (current.tokens) current.tokens.push(["syntax", TAG_SELF_CLS]); | |
current.index += TAG_SELF_CLS.length; | |
return [id, tagsType]; | |
} | |
if (current.pattern[current.index] !== TAG_CLS) | |
throw expected(current, TAG_CLS); | |
if (current.tokens) current.tokens.push(["syntax", TAG_CLS]); | |
++current.index; // move passed > | |
var children = parseAST(current, tagsType); | |
var end = current.index; | |
if ( | |
current.pattern.slice(current.index, current.index + TAG_END.length) !== | |
TAG_END | |
) | |
throw expected(current, TAG_END + id + TAG_CLS); | |
if (current.tokens) current.tokens.push(["syntax", TAG_END]); | |
current.index += TAG_END.length; | |
var closeId = parseId(current, true); | |
if (closeId && current.tokens) current.tokens.push(["id", closeId]); | |
if (id !== closeId) { | |
current.index = end; // rewind for better error message | |
throw expected( | |
current, | |
TAG_END + id + TAG_CLS, | |
TAG_END + closeId + TAG_CLS | |
); | |
} | |
skipWhitespace(current); | |
if (current.pattern[current.index] !== TAG_CLS) | |
throw expected(current, TAG_CLS); | |
if (current.tokens) current.tokens.push(["syntax", TAG_CLS]); | |
++current.index; // move passed > | |
return [id, tagsType, { children: children }]; | |
} | |
function parseId( | |
current /*: Context */, | |
isTag /*:: ?: boolean */ | |
) /*: string */ { | |
var pattern = current.pattern; | |
var length = pattern.length; | |
var id = ""; | |
while (current.index < length) { | |
var char = pattern[current.index]; | |
if ( | |
char === ARG_OPN || | |
char === ARG_CLS || | |
char === ARG_SEP || | |
char === NUM_ARG || | |
char === ESC || | |
isWhitespace(char.charCodeAt(0)) || | |
(isTag && (char === TAG_OPN || char === TAG_CLS || char === "/")) | |
) | |
break; | |
id += char; | |
++current.index; | |
} | |
return id; | |
} | |
function parseSimpleFormat(current /*: Context */) /*: string */ { | |
var start = current.index; | |
var style = parseText(current, "{style}"); | |
if (!style) throw expected(current, "placeholder style name"); | |
if (current.tokens) | |
current.tokens.push([ | |
"style", | |
current.pattern.slice(start, current.index), | |
]); | |
return style; | |
} | |
function parsePluralOffset(current /*: Context */) /*: number */ { | |
var pattern = current.pattern; | |
var length = pattern.length; | |
var offset = 0; | |
if ( | |
pattern.slice(current.index, current.index + OFFSET.length) === OFFSET | |
) { | |
if (current.tokens) | |
current.tokens.push(["offset", "offset"], ["syntax", ":"]); | |
current.index += OFFSET.length; // move passed offset: | |
skipWhitespace(current); | |
var start = current.index; | |
while ( | |
current.index < length && | |
isDigit(pattern.charCodeAt(current.index)) | |
) { | |
++current.index; | |
} | |
if (start === current.index) throw expected(current, "offset number"); | |
if (current.tokens) | |
current.tokens.push(["number", pattern.slice(start, current.index)]); | |
offset = +pattern.slice(start, current.index); | |
} | |
return offset; | |
} | |
function isDigit(code /*: number */) /*: boolean */ { | |
return code >= 0x30 && code <= 0x39; | |
} | |
function parseSubMessages( | |
current /*: Context */, | |
parentType /*: string */ | |
) /*: SubMessages */ { | |
var pattern = current.pattern; | |
var length = pattern.length; | |
var options /*: SubMessages */ = {}; | |
while (current.index < length && pattern[current.index] !== ARG_CLS) { | |
var selector = parseId(current); | |
if (!selector) throw expected(current, "sub-message selector"); | |
if (current.tokens) current.tokens.push(["selector", selector]); | |
skipWhitespace(current); | |
options[selector] = parseSubMessage(current, parentType); | |
skipWhitespace(current); | |
} | |
if (!options.other && submTypes.indexOf(parentType) >= 0) { | |
throw expected( | |
current, | |
null, | |
null, | |
'"other" sub-message must be specified in ' + parentType | |
); | |
} | |
return options; | |
} | |
function parseSubMessage( | |
current /*: Context */, | |
parentType /*: string */ | |
) /*: AST */ { | |
if (current.pattern[current.index] !== ARG_OPN) | |
throw expected(current, ARG_OPN + " to start sub-message"); | |
if (current.tokens) current.tokens.push(["syntax", ARG_OPN]); | |
++current.index; // move passed { | |
var message = parseAST(current, parentType); | |
if (current.pattern[current.index] !== ARG_CLS) | |
throw expected(current, ARG_CLS + " to end sub-message"); | |
if (current.tokens) current.tokens.push(["syntax", ARG_CLS]); | |
++current.index; // move passed } | |
return message; | |
} | |
function expected( | |
current /*: Context */, | |
expected /*:: ?: ?string */, | |
found /*:: ?: ?string */, | |
message /*:: ?: string */ | |
) { | |
var pattern = current.pattern; | |
var lines = pattern.slice(0, current.index).split(/\r?\n/); | |
var offset = current.index; | |
var line = lines.length; | |
var column = lines.slice(-1)[0].length; | |
found = | |
found || | |
(current.index >= pattern.length | |
? "end of message pattern" | |
: parseId(current) || pattern[current.index]); | |
if (!message) message = errorMessage(expected, found); | |
message += " in " + pattern.replace(/\r?\n/g, "\n"); | |
return new SyntaxError(message, expected, found, offset, line, column); | |
} | |
function errorMessage(expected /*: ?string */, found /* string */) { | |
if (!expected) return "Unexpected " + found + " found"; | |
return "Expected " + expected + " but found " + found; | |
} | |
/** | |
* SyntaxError | |
* Holds information about bad syntax found in a message pattern | |
**/ | |
function SyntaxError( | |
message /*: string */, | |
expected /*: ?string */, | |
found /*: ?string */, | |
offset /*: number */, | |
line /*: number */, | |
column /*: number */ | |
) { | |
Error.call(this, message); | |
this.name = "SyntaxError"; | |
this.message = message; | |
this.expected = expected; | |
this.found = found; | |
this.offset = offset; | |
this.line = line; | |
this.column = column; | |
} | |
const createSpan = (content, className) => { | |
const span = document.createElement("span"); | |
span.innerHTML = content; | |
return span; | |
}; | |
const beautifyContent = (tokens, element, error, value) => { | |
tokens.forEach((token) => { | |
const span = createSpan(token[1]); | |
span.style.color = "#386391"; | |
if (token[0] === "text" || token[0] === "space") { | |
element.appendChild(span); | |
return; | |
} | |
switch (token[0]) { | |
case "id": | |
span.id = "argname"; | |
span.style.color = "#e56efa"; | |
span.style.fontStyle = "normal"; | |
break; | |
case "type": | |
span.id = "argtype"; | |
span.style.color = "#9e60f9"; | |
span.style.fontStyle = "normal"; | |
break; | |
case "style": | |
span.id = "argstyle"; | |
span.style.color = "#1eaedb"; | |
span.style.fontStyle = "normal"; | |
break; | |
case "selector": | |
span.id = "argkey"; | |
span.style.color = "#cc80e5"; | |
span.style.fontStyle = "normal"; | |
break; | |
case "syntax": | |
if (token[1] === "#") { | |
span.id = "arghash"; | |
span.style.color = "#1eaedb"; | |
span.style.fontStyle = "normal"; | |
span.style.fontWeight = "normal"; | |
} else { | |
span.id = "arg"; | |
span.style.color = "#7d47b7"; | |
span.style.fontWeight = "normal"; | |
} | |
break; | |
default: | |
span.style.color = "#888"; | |
span.style.fontWeight = "normal"; | |
break; | |
} | |
element.appendChild(span); | |
}); | |
if (error) { | |
const textOffset = value.slice(error.offset); | |
const spanError = createSpan(textOffset); | |
spanError.style.color = "red"; | |
element.appendChild(spanError); | |
} | |
}; | |
const handleSingleton = (text, trads) => { | |
trads.forEach((trad) => { | |
const textarea = trad.querySelector(".expanding"); | |
const text = trad.querySelector(".expanding").textContent; | |
const pre = trad.querySelector(".expanding-clone"); | |
const code = pre.cloneNode({ deep: true }); | |
textarea.style.color = "transparent"; | |
textarea.style.caretColor = "black"; | |
code.style.position = "relative"; | |
code.style.zIndex = "99999999"; | |
code.style.pointerEvents = "none"; | |
code.style.paddingLeft = "25px"; | |
code.style.visibility = "visible"; | |
pre.parentNode.replaceChild(code, pre); | |
const updatePreContent = (value) => { | |
let tokens = []; | |
const wrapper = document.createElement("span"); | |
let error = null; | |
code.innerHTML = ""; | |
try { | |
parse(value, { tokens, tagsType: "<>" }); | |
} catch (e) { | |
console.log(e) | |
error = e; | |
} | |
if (error) { | |
const redCross = document.createElement("span"); | |
redCross.style.background = "#e07472"; | |
redCross.style.position = "absolute"; | |
redCross.style.display = "flex"; | |
redCross.style.justifyContent = "center"; | |
redCross.style.alignItems = "center"; | |
redCross.style.fontSize = "14px"; | |
redCross.style.height = "20px"; | |
redCross.style.width = "20px"; | |
redCross.style.color = "white"; | |
redCross.style.top = "0"; | |
redCross.style.left = "0"; | |
redCross.style.borderRadius = "2rem"; | |
redCross.innerText = "✕"; | |
code.appendChild(redCross); | |
} | |
beautifyContent(tokens, wrapper, error, value); | |
code.appendChild(wrapper); | |
}; | |
if (text) { | |
updatePreContent(text); | |
} | |
textarea.addEventListener("input", ({ target }) => | |
updatePreContent(target.value, trads) | |
); | |
}); | |
}; | |
function isWorkingDocument() { | |
const [_, documentParams] = window.location.pathname.split("/"); | |
const formattedParams = documentParams.split("&").reduce((acc, param) => { | |
const [key, value] = param.split("="); | |
return { ...acc, ...{ [key]: value } }; | |
}, {}); | |
const { ["documents~b"]: documentWorkingStatus, c: documentStatus } = | |
formattedParams; | |
return ( | |
documentWorkingStatus === "working" && (documentStatus === "published" || documentStatus === "unclassified") | |
); | |
} | |
// https://japhy.prismic.io/documents~b=working&c=unclassified&l=fr-fr/YqsoDhEAACIA0btd/ | |
function domUpdate() { | |
const hasTranslationFormatted = document.querySelector( | |
".icu-parsed-translations" | |
); | |
const trads = document.querySelectorAll(".expanding-wrapper"); | |
if (!trads.length > 0 || !isWorkingDocument() || hasTranslationFormatted) { | |
return; | |
} | |
const [firstTradElement] = trads; | |
firstTradElement.classList.add("icu-parsed-translations"); | |
handleSingleton(null, [...trads]); | |
} | |
// Observe nodeList and attribute changes on the page, and | |
// process all unprocessed elements on the page. | |
new MutationObserver(domUpdate).observe(document.body, { | |
childList: true, | |
subtree: true, | |
}); | |
// Initial page | |
domUpdate(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment