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 {