您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A script for interfacing with my Tree Frame UI.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/446506/1076104/Tree%20Frame%202.js
// ==UserScript== // @name Tree Frame 2 // @author Callum Latham <[email protected]> (https://github.com/ctl2/key-based-config) // @exclude * // @description A script for interfacing with my Tree Frame UI. // ==/UserScript== const [getForest, editForest] = (() => { const USER_STYLE_STORAGE_KEY = 'TREE_FRAME_USER_STYLES'; const URL = { 'SCHEME': 'https', 'HOST': 'callumlatham.com', 'PATH': 'tree-frame' }; const STYLE = { 'position': 'fixed', 'height': '100vh', 'width': '100vw' }; let isOpen = false; function getStrippedForest(children) { const stripped = []; for (const child of children) { if (child.isActive === false) { continue; } const data = {}; if ('value' in child) { data.value = child.value; } if ('label' in child) { data.label = child.label; } if ('children' in child) { data.children = getStrippedForest(child.children); } stripped.push(data); } return stripped; } return [ (storageKey, defaultTree) => new Promise(async (resolve, reject) => { if (typeof GM.getValue !== 'function') { reject('The GM.getValue permission is required to retrieve data.'); } GM.getValue(storageKey, defaultTree) .then((tree) => { resolve(getStrippedForest(tree.children)) }); }), (storageKey, rawDefaultTree, title, defaultStyle = {}, frameStyle = {}) => new Promise(async (resolve, reject) => { if (isOpen) { reject('A Tree-Frame iFrame is already open.'); } else if (typeof GM.getValue !== 'function' || typeof GM.setValue !== 'function') { reject('The tree-frame script requires GM.getValue and GM.setValue permissions.'); } else if (typeof storageKey !== 'string' || storageKey === '') { reject(`'${storageKey}' is not a valid storage key.`); } else { isOpen = true; // Setup frame const [targetWindow, frame] = (() => { const frame = document.createElement('iframe'); frame.src = `${URL.SCHEME}://${URL.HOST}/${URL.PATH}`; for (const [property, value] of Object.entries({...STYLE, ...frameStyle})) { frame.style[property] = value; } let targetWindow = window; while (targetWindow !== targetWindow.parent) { targetWindow = targetWindow.parent; } targetWindow.document.body.appendChild(frame); return [targetWindow, frame]; })(); // Retrieve data & await frame load const onMessage = (callback) => { const listener = async (message) => { if (message.origin === `${URL.SCHEME}://${URL.HOST}`) { if (await callback(message.data)) { window.removeEventListener('message', listener); } } }; targetWindow.addEventListener('message', listener); }; const [userTree, userStyles, {'events': EVENTS, password}] = await Promise.all([ GM.getValue(storageKey), GM.getValue(USER_STYLE_STORAGE_KEY, []), new Promise((resolve) => onMessage((data) => { // Wait for a 'ready' response from the iFrame resolve(data); return true; })) ]); // Listen for post-init communication const sendMessage = (message) => { frame.contentWindow.postMessage(message, `${URL.SCHEME}://${URL.HOST}`); }; const closeFrame = () => { return new Promise(resolve => { isOpen = false; frame.remove(); // Wait for the DOM to update setTimeout(resolve, 1); }); }; // Remove functions from tree to enable postMessage transmission const [defaultTree, predicates] = (() => { const getNumberedPredicates = (node, predicateCount) => { const predicates = []; const replacements = {}; for (const property of ['predicate', 'childPredicate', 'descendantPredicate']) { switch (typeof node[property]) { case 'number': throw new Error('Numbers aren\'t valid predicates'); case 'function': replacements[property] = predicateCount++; predicates.push(node[property]); } } if (Array.isArray(node.children)) { replacements.children = []; for (const child of node.children) { const [replacement, childPredicates] = getNumberedPredicates(child, predicateCount); predicateCount += childPredicates.length; predicates.push(...childPredicates); replacements.children.push(replacement); } } if ('seed' in node) { const [replacement, seedPredicates] = getNumberedPredicates(node.seed, predicateCount); predicates.push(...seedPredicates); replacements.seed = replacement; } return [{...node, ...replacements}, predicates]; }; return getNumberedPredicates(rawDefaultTree, 0); })(); onMessage(async (data) => { switch (data.event) { case EVENTS.ERROR: await closeFrame(); reject(data.reason); return true; case EVENTS.PREDICATE: sendMessage({ 'messageId': data.messageId, 'predicateResponse': predicates[data.predicateId]( Array.isArray(data.arg) ? getStrippedForest(data.arg) : data.arg ) }); return false; case EVENTS.STOP: await closeFrame(); // Save changes GM.setValue(storageKey, data.tree); GM.setValue(USER_STYLE_STORAGE_KEY, data.styles); resolve(getStrippedForest(data.tree.children)); return true; default: reject(`Message observed from tree-frame site with unrecognised 'event' value: ${data.event}`); return false; } }); // Pass data try { const config = {password, title, defaultTree, userStyles, defaultStyle}; if (userTree) { config.userTree = userTree; } sendMessage(config); } catch (e) { reject(e.message); } } }) ]; })();