此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.cloud/scripts/446506/1079242/Tree%20Frame.js
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
// ==UserScript==
// @name Tree Frame
// @author Callum Latham <[email protected]> (https://github.com/esc-ism/tree-frame)
// @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 [
(defaultTree, storageKey) => new Promise(async (resolve, reject) => {
if (typeof GM.getValue !== 'function') {
reject('The GM.getValue permission is required to retrieve data.');
}
if (storageKey) {
GM.getValue(storageKey, defaultTree)
.then(({children}) => {
resolve(getStrippedForest(children))
});
} else {
resolve(getStrippedForest(defaultTree.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 ({origin, data}) => {
if (origin === `${URL.SCHEME}://${URL.HOST}`) {
if (await callback(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 ({message}) {
reject(message);
}
}
})
];
})();