90 lines
3.2 KiB
JavaScript
90 lines
3.2 KiB
JavaScript
/*
|
|
* Load the site's JavaScript into a jsdom window. jsdom has no editing
|
|
* commands, so document.execCommand records its calls in window.commands
|
|
* (and does nothing else), and queryCommandState throws, as it does in
|
|
* some browsers.
|
|
*/
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const { JSDOM } = require("jsdom");
|
|
|
|
const ROOT = path.resolve(__dirname, "..", "..");
|
|
|
|
// The story field of publish/write.inc.php, in a form
|
|
const STORY_FORM = `
|
|
<form id="form">
|
|
<label for="title">Title:</label><input id="title" name="title">
|
|
<label for="author">Author:</label><input id="author" name="author">
|
|
<label for="email">Email address:</label><input id="email" name="email">
|
|
<label for="story">Story:</label>
|
|
<div id="textparent"><textarea id="story" name="story"></textarea></div>
|
|
<div id="uploadparent" style="display: none;"><input type="file" id="upload" name="upload"></div>
|
|
<span id="wordcounter" class="counter" style="display: none;">0 words</span>
|
|
</form>`;
|
|
|
|
function load(scripts, body, story) {
|
|
const dom = new JSDOM(`<!DOCTYPE html><html><body>${body}</body></html>`, {
|
|
runScripts: "outside-only",
|
|
url: "https://xxx.org.za/publish/",
|
|
pretendToBeVisual: true,
|
|
});
|
|
const window = dom.window;
|
|
window.commands = [];
|
|
window.document.execCommand = function (command, ui, value) {
|
|
window.commands.push([command, value]);
|
|
return true;
|
|
};
|
|
window.document.queryCommandState = function () {
|
|
throw new Error("not supported");
|
|
};
|
|
if (story !== undefined) window.document.getElementById("story").value = story;
|
|
for (const script of scripts) window.eval(fs.readFileSync(path.join(ROOT, script), "utf8"));
|
|
return window;
|
|
}
|
|
|
|
// A window with the story form and publish/editor.js, and the editor attached
|
|
function editorWindow(story = "") {
|
|
const window = load(["publish/editor.js"], STORY_FORM, story);
|
|
const textarea = window.document.getElementById("story");
|
|
const editor = window.XxxEditor.attach(textarea);
|
|
const box = window.document.querySelector(".xed-area");
|
|
return { window, document: window.document, textarea, editor, box };
|
|
}
|
|
|
|
// Select from (node, offset) to (endNode, endOffset), or put the caret there
|
|
function select(window, node, offset, endNode = node, endOffset = offset) {
|
|
const range = window.document.createRange();
|
|
range.setStart(node, offset);
|
|
range.setEnd(endNode, endOffset);
|
|
const selection = window.getSelection();
|
|
selection.removeAllRanges();
|
|
selection.addRange(range);
|
|
return range;
|
|
}
|
|
|
|
// Select all the text of an element
|
|
function selectText(window, element) {
|
|
const range = window.document.createRange();
|
|
range.selectNodeContents(element);
|
|
const selection = window.getSelection();
|
|
selection.removeAllRanges();
|
|
selection.addRange(range);
|
|
return range;
|
|
}
|
|
|
|
function button(document, label) {
|
|
const found = document.querySelector(`.xed-btn[aria-label="${label}"]`);
|
|
if (!found) throw new Error(`No button "${label}"`);
|
|
return found;
|
|
}
|
|
|
|
function key(window, target, key, options = {}) {
|
|
const event = new window.KeyboardEvent("keydown", Object.assign({ key, bubbles: true, cancelable: true }, options));
|
|
target.dispatchEvent(event);
|
|
return event;
|
|
}
|
|
|
|
module.exports = { ROOT, STORY_FORM, load, editorWindow, select, selectText, button, key };
|