diff --git a/packages/core/src/projects/common.ts b/packages/core/src/projects/common.ts index 1666b2b2..a28b7a7e 100644 --- a/packages/core/src/projects/common.ts +++ b/packages/core/src/projects/common.ts @@ -5,16 +5,18 @@ import { getAllRawConfigs } from '../utils/common'; import cloneDeep from 'lodash/cloneDeep'; import { $message, $model } from './init'; import { el } from '../utils/dom'; -import { getValue, notification, setValue } from '../utils/tampermonkey'; +import { notification } from '../utils/tampermonkey'; import { parseAnswererWrappers } from '../core/utils/common'; import { AnswererWrapper, defaultAnswerWrapperHandler } from '../core/worker/answer.wrapper.handler'; import debounce from 'lodash/debounce'; +import { WorkResult } from '../core/worker/interface'; +import { $creator } from '../utils/creator'; -export const CommonProject: Project = { +export const CommonProject = Project.create({ name: '通用', domains: [], - scripts: [ - new Script({ + scripts: { + onlineSearch: new Script({ name: '在线搜题', url: [/.*/], namespace: 'common.online-search', @@ -35,8 +37,8 @@ export const CommonProject: Project = { if (this.cfg.selectSearch) { document.addEventListener( 'selectionchange', - debounce(function () { - setValue('common.online-search.selection', document.getSelection()?.toString() || ''); + debounce(() => { + this.cfg.selection = document.getSelection()?.toString() || ''; }, 500) ); } @@ -55,7 +57,7 @@ export const CommonProject: Project = { if (value) { const t = Date.now(); - const results = await defaultAnswerWrapperHandler(getValue('common.settings.answererWrappers'), { + const results = await defaultAnswerWrapperHandler(CommonProject.scripts.settings.cfg.answererWrappers, { title: value }); // 耗时计算 @@ -103,7 +105,208 @@ export const CommonProject: Project = { ); } }), - new Script({ + workResults: new Script({ + name: '搜索结果', + url: [/.*/], + namespace: 'common.work-results', + configs: { + notes: { + defaultValue: '点击题目序号,查看搜索结果
每次自动答题开始前,都会清空上一次的搜索结果。' + }, + /** + * 显示类型 + * list: 显示为题目列表 + * numbers: 显示为序号列表 + */ + type: { + label: '显示类型', + tag: 'select', + defaultValue: 'numbers' as 'questions' | 'numbers', + onload() { + this.append( + ...$creator.selectOptions(this.getAttribute('value'), [ + ['numbers', '序号列表'], + ['questions', '题目列表'] + ]) + ); + } + }, + results: { + defaultValue: [] as WorkResult[] + }, + currentResultIndex: { + defaultValue: 0 + } + }, + onrender({ panel }) { + /** 渲染结果面板 */ + const render = () => { + if (this.cfg.results.length) { + // 如果序号指向的结果为空,则代表已经被清空,则重新让index变成0 + if (this.cfg.results[this.cfg.currentResultIndex] === undefined) { + this.cfg.currentResultIndex = 0; + } + + // 渲染序号或者题目列表 + if (this.cfg.type === 'numbers') { + const resultContainer = el('div', {}, (res) => { + res.style.width = '400px'; + }); + const list = el('div', {}, (list) => { + list.style.maxWidth = '400px'; + list.style.marginBottom = '12px'; + }); + /** 渲染序号 */ + const nums = this.cfg.results.map((result, index) => { + return el('span', { className: 'search-results-num', innerText: (index + 1).toString() }, (num) => { + if (result.error) { + num.classList.add('error'); + } else if (index === this.cfg.currentResultIndex) { + num.classList.add('active'); + } + + num.onclick = () => { + for (const n of nums) { + n.classList.remove('active'); + } + num.classList.add('active'); + // 更新显示序号 + this.cfg.currentResultIndex = index; + // 重新渲染结果列表 + resultContainer.replaceChildren(createResult(result)); + }; + }); + }); + + list.replaceChildren(...nums); + // 初始显示指定序号的结果 + resultContainer.replaceChildren(createResult(this.cfg.results[this.cfg.currentResultIndex])); + + panel.body.replaceChildren(el('hr'), list, resultContainer); + } else { + const resultContainer = el('div', {}, (res) => { + res.style.width = '50%'; + }); + const list = el('div', {}, (list) => { + list.style.width = '50%'; + + const resize = () => { + if (list.parentElement === null) { + window.removeEventListener('reset', resize); + } else { + list.style.maxHeight = window.innerHeight / 2 + 'px'; + } + }; + resize(); + window.addEventListener('resize', resize); + }); + + /** 渲染题目列表 */ + const questions = this.cfg.results.map((result, index) => { + return el( + 'span', + + [ + el('span', { innerHTML: (index + 1 < 10 ? index + 1 + ' ' : index + 1).toString() }, (num) => { + num.style.marginRight = '12px'; + num.style.display = 'inline-block'; + }), + result.ctx?.elements.title?.map((t) => t.innerText || '题目为空').join(',') || '题目为空' + ], + (question) => { + question.className = 'search-results-question'; + + if (result.error) { + question.classList.add('error'); + } else if (index === this.cfg.currentResultIndex) { + question.classList.add('active'); + } + + question.onmouseover = () => { + question.classList.add('hover'); + // 重新渲染结果列表 + resultContainer.replaceChildren(createResult(result)); + }; + + question.onmouseleave = () => { + question.classList.remove('hover'); + // 重新显示指定序号的结果 + resultContainer.replaceChildren(createResult(this.cfg.results[this.cfg.currentResultIndex])); + }; + + question.onclick = () => { + for (const q of questions) { + q.classList.remove('active'); + } + question.classList.add('active'); + // 更新显示序号 + this.cfg.currentResultIndex = index; + // 重新渲染结果列表 + resultContainer.replaceChildren(createResult(result)); + }; + } + ); + }); + + list.replaceChildren(...questions); + // 初始显示指定序号的结果 + resultContainer.replaceChildren(createResult(this.cfg.results[this.cfg.currentResultIndex])); + + panel.body.replaceChildren( + el('hr'), + el( + 'div', + [ + list, + el('div', {}, (border) => { + border.style.borderRight = '1px solid #63636346'; + border.style.margin = '0px 8px'; + }), + resultContainer + ], + (div) => { + div.style.maxWidth = '800px'; + div.style.display = 'flex'; + } + ) + ); + } + } else { + panel.body.replaceChildren('暂无任何搜索结果'); + } + }; + + /** 渲染结果列表 */ + const createResult = (result: WorkResult) => { + if (result.error) { + return el( + 'div', + result.error.message + ? result.error.message + : result.result?.finish === false + ? '此题未完成, 可能是没有匹配的选项。' + : result.ctx?.searchResults?.length === 0 + ? '此题未搜索到答案' + : '未知的错误', + (el) => { + el.style.textAlign = 'center'; + el.style.color = 'red'; + } + ); + } else { + return el('search-results-element', { + results: result.ctx?.searchResults || [], + question: result.ctx?.elements.title?.map((t) => t.innerText).join(',') + }); + } + }; + + render(); + this.onConfigChange('type', render); + this.onConfigChange('results', render); + } + }), + settings: new Script({ name: '全局设置', url: [/.*/], namespace: 'common.settings', @@ -118,7 +321,7 @@ export const CommonProject: Project = { } }, answererWrappers: { - defaultValue: [] + defaultValue: [] as AnswererWrapper[] }, answererWrappersButton: { @@ -128,22 +331,24 @@ export const CommonProject: Project = { type: 'button' }, onload() { - const aws: any[] = getValue('common.settings.answererWrappers'); + const aws: any[] = CommonProject.scripts.settings.cfg.answererWrappers; this.value = `点击重新配置${aws.length ? ',当前有' + aws.length + '个可用题库' : ''}`; this.onclick = () => { - const aw: any[] = getValue('common.settings.answererWrappers'); + const aw: any[] = CommonProject.scripts.settings.cfg.answererWrappers; + const copy = $creator.copy('复制题库配置', JSON.stringify(aw)); + const list = el('div', [ - el('div', aw.length ? '以下是已经解析过的题库配置:' : ''), + el('div', aw.length ? ['以下是已经解析过的题库配置:', copy] : ''), ...createAnswererWrapperList(aw) ]); const model = $model('prompt', { - content: el('div', { - innerHTML: - '具体配置教程,请查看官网的 "自动答题教程"' + - list.innerHTML - }).innerHTML, + content: el('div', [ + '具体配置教程,请查看官网:', + el('a', { href: 'https://docs.ocsjs.com/docs/work' }, '自动答题教程'), + list + ]), placeholder: aw.length ? '重新输入' : '输入题库配置', cancelButton: el('button', { className: 'model-cancel-button', @@ -151,14 +356,15 @@ export const CommonProject: Project = { onclick() { $message('success', { content: '已清空,在答题前请记得重新配置。' }); model?.remove(); - setValue('common.settings.answererWrappers', []); + CommonProject.scripts.settings.cfg.answererWrappers = []; } }), async onConfirm(value) { if (value) { try { const aw = await parseAnswererWrappers(value); - setValue('common.settings.answererWrappers', aw); + CommonProject.scripts.settings.cfg.answererWrappers = aw; + $model('alert', { content: el('div', [ el('div', '配置成功,打开具有答题脚本的页面后即可自动答题,解析到的题库如下所示:'), @@ -206,7 +412,7 @@ export const CommonProject: Project = { }); } }), - new Script({ + guide: new Script({ name: '使用教程', url: [/.*/], namespace: 'common.guide', @@ -235,17 +441,17 @@ export const CommonProject: Project = { el('summary', project.name), el( 'ul', - project.scripts.map((script) => + Object.keys(project.scripts).map((key) => el('li', [ el( 'span', { title: [ - '隐藏操作页面:\t' + (script.hideInPanel ? '是' : '否'), - '在以下页面中运行:\t' + script.url.join(',') + '隐藏操作页面:\t' + (project.scripts[key].hideInPanel ? '是' : '否'), + '在以下页面中运行:\t' + project.scripts[key].url.join(',') ].join('\n') }, - script.name + project.scripts[key].name ) ]) ), @@ -287,7 +493,7 @@ export const CommonProject: Project = { } } }), - new Script({ + dev: new Script({ name: '开发者调试页面', // 使用时从打包中的代码修改此处为 true hideInPanel: true, @@ -296,7 +502,7 @@ export const CommonProject: Project = { const configs = cloneDeep( getAllRawConfigs( getDefinedProjects() - .map((p) => p.scripts) + .map((p) => Object.keys(p.scripts).map((key) => p.scripts[key])) .flat() .filter((s) => s.name !== '开发者调试页面') ) @@ -313,7 +519,7 @@ export const CommonProject: Project = { return configs; } }), - new Script({ + hack: new Script({ name: '页面复制粘贴限制解除脚本', url: [/.*/], hideInPanel: true, @@ -325,8 +531,8 @@ export const CommonProject: Project = { setTimeout(() => enableCopy(), 3000); } }) - ] -}; + } +}); function enableCopy() { try {