Skip to content

Commit

Permalink
feat(core): 完成脚本内容的元素和数值同步
Browse files Browse the repository at this point in the history
  • Loading branch information
enncy committed Nov 8, 2022
1 parent 81b1daf commit 4d1dc60
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 44 deletions.
43 changes: 33 additions & 10 deletions packages/core/src/interfaces/script.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { namespaceKey } from '../utils/common';
import { getValue, namespaceKey, setValue } from '../utils/common';
import { Config } from './config';

export type ScriptConfigsProvider<T extends Record<string, Config>> = T | { (): T };
Expand All @@ -24,26 +24,27 @@ export class BaseScript {
onbeforeunload?: (...args: any) => any;
}

export interface ScriptEvent {
[name: string]: any;
}
export type ScriptConfigs = {
/** 脚本提示 */
readonly notes?: {
defaultValue: string;
};
} & Record<string, Config>;

/**
* 脚本
*/
export class Script<T extends Record<string, Config> = Record<string, Config>> extends BaseScript {
export class Script<T extends ScriptConfigs = ScriptConfigs> extends BaseScript {
/** 名字 */
name: string;
/** 匹配路径 */
url: (string | RegExp)[];
/** 唯一命名空间,用于避免 config 重名 */
namespace?: string;
/** 脚本提示 */
notes: string[];
/** 后台脚本(不提供管理页面) */
hideInPanel?: boolean;
/** 通过 configs 映射并经过解析后的配置对象 */
cfg: Record<keyof T, any> = {} as any;
cfg: Record<keyof T, any> & { notes?: string } = {} as any;
/** 未经处理的 configs 原对象 */
private _configs?: ScriptConfigsProvider<T>;
/** 存储已经处理过的 configs 对象,避免重复调用方法 */
Expand All @@ -69,22 +70,44 @@ export class Script<T extends Record<string, Config> = Record<string, Config>> e
hideInPanel,
onstart,
onactive,
oncomplete
oncomplete,
onbeforeunload
}: ScriptOptions<T> & {
onstart?: (this: Script<T>, ...args: any) => any;
onactive?: (this: Script<T>, ...args: any) => any;
oncomplete?: (this: Script<T>, ...args: any) => any;
onbeforeunload?: (this: Script<T>, ...args: any) => any;
}) {
super();
this.name = name;
this.namespace = namespace;
this.url = url;
this.notes = notes || [];
this._configs = configs;
this.hideInPanel = hideInPanel;
this.onstart = onstart as any;
this.onactive = onactive as any;
this.oncomplete = oncomplete as any;
this.onbeforeunload = onbeforeunload as any;

const _onstart = this.onstart;
this.onstart = (...args: any) => {
_onstart?.call(this, ...args);
const urls: string[] = Array.from(getValue('_urls_'));
const urlHasInRecord = urls.find((u) => u === location.href);
if (!urlHasInRecord) {
setValue('_urls_', urls.concat(location.href));
}
};

const _onbeforeunload = this.onbeforeunload;
this.onbeforeunload = (...args: any) => {
_onbeforeunload?.call(this, ...args);
const urls: string[] = Array.from(getValue('_urls_'));
const urlIndex = urls.findIndex((u) => u === location.href);
if (urlIndex !== -1) {
setValue('_urls_', urls.splice(urlIndex, 1));
}
};
}

onConfigChange(key: keyof T, handler: (pre: any, curr: any, remote: boolean) => any) {
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/projects/cx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ export const CXProject: Project = {
name: '章节提示',
namespace: 'cx.chapter',
url: [/\/mooc2-ans\/mycourse\/studentcourse/],
notes: ['请点击任意一个章节进入课程,5秒后自动进入。'],
configs: {
notes: { defaultValue: `请点击任意一个章节进入课程\n5秒后将自动进入。` }
},
oncomplete() {
const list = $$el('.catalog_task .catalog_jindu');

if (list.length) {
list[0].click();
} else {
this.cfg.notes = '全部任务已完成!';
$model('prompt', {
content: '全部任务已完成!',
onConfirm(val) {
Expand All @@ -31,8 +34,9 @@ export const CXProject: Project = {
name: '课程学习',
namespace: 'cx.study',
url: [/\/mycourse\/studentstudy/],
notes: ['测试', '111'],

configs: {
notes: { defaultValue: `测试aaaa` },
test: {
defaultValue: 1,
attrs: { type: 'number', title: '测试选项' },
Expand Down
36 changes: 16 additions & 20 deletions packages/core/src/projects/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Config } from '../interfaces/config';
import { cors } from '../interfaces/cors';
import { Project } from '../interfaces/project';
import { Script } from '../interfaces/script';
import { getMatchedScripts, namespaceKey } from '../utils/common';
import { getMatchedScripts, getValue, namespaceKey } from '../utils/common';
import { el, enableElementDraggable, tooltip } from '../utils/dom';
import { StartConfig } from '../utils/start';
import { humpToTarget } from '../utils/string';
Expand All @@ -19,7 +19,8 @@ export type ModelAttrs = Pick<
title?: string;
};

const modelContainer = el('div', { className: 'model-container' });
const panel = el('div');
const root = panel.attachShadow({ mode: 'closed' });
const messageContainer = el('div', { className: 'message-container' });

const InitPanelScript = new Script({
Expand All @@ -46,7 +47,7 @@ const InitPanelScript = new Script({
}
const visibleProjects = projects.filter((p) => p.scripts.find((s) => !s.hideInPanel));
/** 当前匹配到的脚本,并且面板不隐藏 */
const matchedScripts = getMatchedScripts(projects).filter((s) => !s.hideInPanel);
const matchedScripts = getMatchedScripts(projects, [location.href]).filter((s) => !s.hideInPanel);

const container = el('container-element');

Expand Down Expand Up @@ -85,7 +86,7 @@ const InitPanelScript = new Script({
},
(select) => {
for (const project of visibleProjects) {
const scripts = getMatchedScripts([project]).filter((s) => !s.hideInPanel);
const scripts = getMatchedScripts([project], getValue('_urls_') || []).filter((s) => !s.hideInPanel);
for (const script of scripts) {
select.append(
el('option', {
Expand Down Expand Up @@ -153,7 +154,7 @@ const InitPanelScript = new Script({
for (const project of visibleProjects.sort((a, b) =>
a.scripts.some((s) => a.name + '-' + s.name === this.cfg.currentPanelName) ? -1 : 1
)) {
const scripts = getMatchedScripts([project]);
const scripts = getMatchedScripts([project], getValue('_urls_') || [location.href]);

for (const script of scripts) {
const scriptContainer = el('div', { className: 'script-panel' });
Expand All @@ -163,7 +164,13 @@ const InitPanelScript = new Script({
const configsContainer = el('div', { className: 'configs card', title: '脚本设置' });
const configsBody = el('div', { className: 'configs-body' });

notesContainer.append(...createNotes(script));
script.onConfigChange('notes', (pre, curr) => {
console.log('change', curr);

notesContainer.replaceChildren(...createNotes(script));
});

notesContainer.replaceChildren(...createNotes(script));
configsBody.append(...createConfigs(script.namespace, script.configs || {}));
configsContainer.append(configsBody);

Expand Down Expand Up @@ -267,18 +274,8 @@ const InitPanelScript = new Script({
/** 创建内容板块 */
const createNotes = (script: Script) => {
const notes: HTMLDivElement[] = [];
script.notes = script.notes || [];

/** 创建响应式对象,当 notes 改变时,页面元素内容同样改变 */
script.notes = new Proxy(script.notes, {
set(target, key, value) {
const note: HTMLDivElement = Reflect.get(notes, key);
note.innerText = value;
return Reflect.set(target, key, value);
}
});

for (const note of script.notes || []) {
for (const note of script.cfg.notes?.split('\n') || []) {
notes.push(el('div', { textContent: note }));
}
return notes;
Expand Down Expand Up @@ -308,8 +305,7 @@ const InitPanelScript = new Script({
/** 在顶级页面显示操作面板 */
if (matchedScripts.length !== 0 && self === top) {
// 随机位置插入操作面板到页面
const panel = el('div');
panel.attachShadow({ mode: 'closed' }).append(modelContainer, container);
root.append(container);
document.body.children[random(0, document.body.children.length - 1)].after(panel);

render();
Expand All @@ -335,7 +331,7 @@ export function $model(type: ModelElement['type'], attrs: ModelAttrs) {
if (self === top) {
const { disableWrapperCloseable, onConfirm, onCancel, ..._attrs } = attrs;

modelContainer.append(
root.append(
el('div', { className: 'model-wrapper' }, (wrapper) => {
const model = el('model-element', {
onConfirm(val) {
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/projects/zhs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ export const ZHSProject: Project = {
new Script({
name: '课程学习',
url: [/.*/],
notes: ['111'],
namespace: 'zhs.study'

namespace: 'zhs.study',
configs: {
notes: {
defaultValue: `智慧树111`
}
}
})
]
};
16 changes: 10 additions & 6 deletions packages/core/src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { Script } from '../interfaces/script';
*/
export function getValue(key: string, defaultValue?: any) {
// eslint-disable-next-line no-undef
const val = GM_getValue(key, defaultValue);
return typeof val === 'undefined' ? '' : val;
return GM_getValue(key, defaultValue);
}

export function deleteValue(key: string) {
Expand Down Expand Up @@ -73,11 +72,11 @@ export function removeConfigChangeListener(listenerId: number) {
* @param projects 程序列表
* @returns
*/
export function getMatchedScripts(projects: Project[]) {
export function getMatchedScripts(projects: Project[], urls: string[]) {
const scripts = [];
for (const project of projects) {
for (const script of project.scripts) {
if (script.url.some((u) => RegExp(u).test(document.location.href))) {
if (script.url.some((u) => urls.some((url) => RegExp(u).test(url)))) {
scripts.push(script);
}
}
Expand All @@ -100,7 +99,7 @@ export function namespaceKey(namespace: string | undefined, key: any) {
* @param script
* @returns
*/
export function createConfigProxy<T extends Record<string, Config> = Record<string, Config>>(script: Script<T>) {
export function createConfigProxy(script: Script) {
const proxy = new Proxy(script.cfg, {
set(target, propertyKey, value) {
const key = namespaceKey(script.namespace, propertyKey);
Expand All @@ -118,9 +117,14 @@ export function createConfigProxy<T extends Record<string, Config> = Record<stri
for (const key in script.configs) {
if (Object.prototype.hasOwnProperty.call(script.configs, key)) {
const element = Reflect.get(script.configs, key);
Reflect.set(proxy, key, getValue(namespaceKey(script.namespace, key), element.defaultValue));
Reflect.set(proxy, key, getValue(namespaceKey(script.namespace, key)) || element.defaultValue);
}
}

if (script.namespace) {
// 重置特殊的 notes 对象
proxy.notes = script.configs?.notes?.defaultValue;
}

return proxy;
}
15 changes: 11 additions & 4 deletions packages/core/src/utils/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,18 @@ export interface StartConfig {
* @param cfg 启动配置
*/
export function start(cfg: StartConfig) {
const scripts = getMatchedScripts(cfg.projects);
scripts.forEach((script) => {
/** 为对象添加响应式特性,在设置值的时候同步到本地存储中 */
script.cfg = createConfigProxy(script);
/** 为对象添加响应式特性,在设置值的时候同步到本地存储中 */
cfg.projects = cfg.projects.map((p) => {
p.scripts = p.scripts.map((s) => {
s.cfg = createConfigProxy(s);
return s;
});
return p;
});

const scripts = getMatchedScripts(cfg.projects, [location.href]);

scripts.forEach((script) => {
/** 执行脚本 */
script.onstart?.(cfg);

Expand Down

0 comments on commit 4d1dc60

Please sign in to comment.