Skip to content

Instantly share code, notes, and snippets.

@blaadje
Last active August 24, 2022 13:29
Show Gist options
  • Save blaadje/d5c054b85033a7c96ec80dfc7c0d47a1 to your computer and use it in GitHub Desktop.
Save blaadje/d5c054b85033a7c96ec80dfc7c0d47a1 to your computer and use it in GitHub Desktop.
// ==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