clickgo 6.9.0 → 6.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +16 -0
- package/README.md +4 -0
- package/dist/app/demo.cga +0 -0
- package/dist/app/task.cga +0 -0
- package/dist/clickgo.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/lib/command/native.d.ts +18 -0
- package/dist/lib/command/palette.d.ts +10 -0
- package/dist/lib/command/schema.d.ts +31 -0
- package/dist/lib/command.d.ts +218 -0
- package/dist/lib/native.d.ts +12 -0
- package/dist/lib/task.d.ts +2 -0
- package/doc/clickgo-rag.md +2376 -537
- package/package.json +9 -3
- package/test/core-module-loading.mjs +0 -169
- package/test/desktop.mjs +0 -715
- package/test/dock-lifecycle.mjs +0 -280
- package/test/dom-style.mjs +0 -261
- package/test/form-launcher.mjs +0 -177
- package/test/form-modal-layer.mjs +0 -75
- package/test/form-native-regions.mjs +0 -115
- package/test/form-native-style.mjs +0 -80
- package/test/form-native.mjs +0 -68
- package/test/form-notify-animation.mjs +0 -102
- package/test/form-notify.mjs +0 -90
- package/test/form-viewport.mjs +0 -127
- package/test/iconview.mjs +0 -70
- package/test/jodit-lifecycle.mjs +0 -212
- package/test/locale-integrity.mjs +0 -189
- package/test/pop-max-size.mjs +0 -66
- package/test/rtl-archive.mjs +0 -440
- package/test/rtl-direction.mjs +0 -193
- package/test/scroll.mjs +0 -145
- package/test/select.mjs +0 -885
- package/test/sidebar-toggle.mjs +0 -75
- package/test/task-package-loading.mjs +0 -94
- package/test/task-start.mjs +0 -141
- package/test/task-tray.mjs +0 -331
- package/test/tool-assets.mjs +0 -64
- package/test/tool-clone.mjs +0 -42
- package/test/toolbox.mjs +0 -88
- package/test/tuieditor-locale.mjs +0 -175
- package/test/vflow.mjs +0 -122
- package/test/web-drag-mask.mjs +0 -96
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
// Run: node --experimental-vm-modules test/locale-integrity.mjs
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { access, readdir, readFile } from 'node:fs/promises';
|
|
4
|
-
import { SourceTextModule } from 'node:vm';
|
|
5
|
-
import { JSDOM } from 'jsdom';
|
|
6
|
-
import ts from 'typescript';
|
|
7
|
-
|
|
8
|
-
/** --- ClickGo 的固定内置语言顺序 --- */
|
|
9
|
-
const codes = [
|
|
10
|
-
'sc', 'tc', 'ja', 'ko', 'th', 'vi', 'ar', 'id',
|
|
11
|
-
'en', 'es', 'de', 'fr', 'pt', 'ru', 'it', 'tr',
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
/** --- 仍使用问号占位符、会在运行时被替换的语言键 --- */
|
|
15
|
-
const placeholderKeys = new Set([
|
|
16
|
-
'dist/lib/fs.ts:apply-unmount',
|
|
17
|
-
'dist/lib/storage.ts:sure-clear',
|
|
18
|
-
'dist/sources/control/page/code.ts:total-of',
|
|
19
|
-
]);
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* --- 获取对象字面量的静态属性名 ---
|
|
23
|
-
* @param property TypeScript 属性节点
|
|
24
|
-
* @returns 属性名;动态属性返回空字符串
|
|
25
|
-
*/
|
|
26
|
-
function getPropertyName(property) {
|
|
27
|
-
if (!ts.isPropertyAssignment(property) || !property.name) {
|
|
28
|
-
return '';
|
|
29
|
-
}
|
|
30
|
-
if (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name)) {
|
|
31
|
-
return property.name.text;
|
|
32
|
-
}
|
|
33
|
-
return '';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* --- 从对象字面量取静态属性 ---
|
|
38
|
-
* @param object 对象字面量
|
|
39
|
-
* @param key 属性名
|
|
40
|
-
* @returns 属性初始化节点
|
|
41
|
-
*/
|
|
42
|
-
function getProperty(object, key) {
|
|
43
|
-
return object.properties.find((property) => getPropertyName(property) === key)?.initializer;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* --- 断言翻译值是非空静态字符串 ---
|
|
48
|
-
* @param value 翻译的 AST 节点
|
|
49
|
-
* @param message 失败信息
|
|
50
|
-
*/
|
|
51
|
-
function assertText(value, message) {
|
|
52
|
-
const isText = ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value);
|
|
53
|
-
assert.equal(isText, true, `${message} must be a static string literal`);
|
|
54
|
-
assert.notEqual(value.text.trim(), '', `${message} must not be empty`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* --- 从 TypeScript 源码收集含英文条目的语言表 ---
|
|
59
|
-
* @param path TypeScript 文件路径
|
|
60
|
-
* @returns 语言表及其行号
|
|
61
|
-
*/
|
|
62
|
-
async function collectLocaleTables(path) {
|
|
63
|
-
const source = ts.createSourceFile(path, await readFile(path, 'utf8'), ts.ScriptTarget.Latest, true);
|
|
64
|
-
const tables = [];
|
|
65
|
-
const visit = (node) => {
|
|
66
|
-
if (ts.isObjectLiteralExpression(node) && getProperty(node, 'en')) {
|
|
67
|
-
const keys = node.properties.map(getPropertyName);
|
|
68
|
-
if (codes.every((code) => keys.includes(code))) {
|
|
69
|
-
tables.push({
|
|
70
|
-
'node': node,
|
|
71
|
-
'line': source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
ts.forEachChild(node, visit);
|
|
76
|
-
};
|
|
77
|
-
visit(source);
|
|
78
|
-
return tables;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const dom = new JSDOM('<!doctype html><html><head></head><body></body></html>');
|
|
82
|
-
for (const name of ['window', 'document', 'navigator']) {
|
|
83
|
-
Object.defineProperty(globalThis, name, {
|
|
84
|
-
'configurable': true,
|
|
85
|
-
'value': dom.window[name],
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// The language registry is executed from TypeScript source, not generated JavaScript.
|
|
90
|
-
const toolSource = await readFile(new URL('../dist/lib/tool.ts', import.meta.url), 'utf8');
|
|
91
|
-
const toolModule = new SourceTextModule(ts.transpileModule(toolSource, {
|
|
92
|
-
'compilerOptions': {
|
|
93
|
-
'target': ts.ScriptTarget.ES2022,
|
|
94
|
-
'module': ts.ModuleKind.ESNext,
|
|
95
|
-
},
|
|
96
|
-
}).outputText);
|
|
97
|
-
await toolModule.link(() => {
|
|
98
|
-
throw new Error('tool.ts must not import runtime modules');
|
|
99
|
-
});
|
|
100
|
-
await toolModule.evaluate();
|
|
101
|
-
|
|
102
|
-
const { lang } = toolModule.namespace;
|
|
103
|
-
assert.deepEqual(lang.codes, codes, 'language codes must retain all 16 supported locales');
|
|
104
|
-
assert.equal(lang.names.length, codes.length, 'every locale must have a display name');
|
|
105
|
-
assert.equal(new Set(lang.names).size, codes.length, 'language display names must be unique');
|
|
106
|
-
assert.deepEqual(Object.keys(lang.tags).sort(), [...codes].sort(), 'every locale must have an HTML lang tag');
|
|
107
|
-
for (const code of codes) {
|
|
108
|
-
assert.notEqual(lang.names[lang.codes.indexOf(code)].trim(), '', `${code} must have a non-empty display name`);
|
|
109
|
-
assert.notEqual(lang.getTag(code).trim(), '', `${code} must resolve to a non-empty HTML lang tag`);
|
|
110
|
-
assert.equal(lang.getDirection(code), code === 'ar' ? 'rtl' : 'ltr', `${code} must resolve to its writing direction`);
|
|
111
|
-
}
|
|
112
|
-
assert.equal(lang.getDirection('ar-EG'), 'rtl', 'Arabic HTML tags must resolve to RTL');
|
|
113
|
-
assert.equal(lang.getDirection('en-US'), 'ltr', 'non-Arabic HTML tags must resolve to LTR');
|
|
114
|
-
|
|
115
|
-
const controlRoot = new URL('../dist/sources/control/', import.meta.url);
|
|
116
|
-
const controlNames = (await readdir(controlRoot, { 'withFileTypes': true }))
|
|
117
|
-
.filter((item) => item.isDirectory())
|
|
118
|
-
.map((item) => item.name)
|
|
119
|
-
.sort();
|
|
120
|
-
const controlSources = [];
|
|
121
|
-
for (const name of controlNames) {
|
|
122
|
-
const path = `dist/sources/control/${name}/code.ts`;
|
|
123
|
-
try {
|
|
124
|
-
await access(path);
|
|
125
|
-
controlSources.push(path);
|
|
126
|
-
}
|
|
127
|
-
catch {}
|
|
128
|
-
}
|
|
129
|
-
const localeSources = [
|
|
130
|
-
'dist/lib/form.ts',
|
|
131
|
-
'dist/lib/fs.ts',
|
|
132
|
-
'dist/lib/storage.ts',
|
|
133
|
-
'dist/lib/task.ts',
|
|
134
|
-
...controlSources,
|
|
135
|
-
];
|
|
136
|
-
|
|
137
|
-
const tables = [];
|
|
138
|
-
for (const path of localeSources) {
|
|
139
|
-
for (const table of await collectLocaleTables(path)) {
|
|
140
|
-
tables.push({ ...table, path });
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
assert.equal(tables.length, 26, 'all 26 built-in TypeScript locale tables must be present');
|
|
144
|
-
for (const { node, path, line } of tables) {
|
|
145
|
-
const tableCodes = node.properties.map(getPropertyName);
|
|
146
|
-
assert.deepEqual([...tableCodes].sort(), [...codes].sort(), `${path}:${line} must contain exactly 16 locale entries`);
|
|
147
|
-
const english = getProperty(node, 'en');
|
|
148
|
-
assert.equal(ts.isObjectLiteralExpression(english), true, `${path}:${line} English locale must be an object`);
|
|
149
|
-
const keys = english.properties.map(getPropertyName);
|
|
150
|
-
assert.equal(new Set(keys).size, keys.length, `${path}:${line} English locale must not repeat keys`);
|
|
151
|
-
for (const code of codes) {
|
|
152
|
-
const localized = getProperty(node, code);
|
|
153
|
-
assert.equal(ts.isObjectLiteralExpression(localized), true, `${path}:${line} ${code} locale must be an object`);
|
|
154
|
-
const localizedKeys = localized.properties.map(getPropertyName);
|
|
155
|
-
assert.deepEqual([...localizedKeys].sort(), [...keys].sort(), `${path}:${line} ${code} locale keys must match English`);
|
|
156
|
-
for (const key of keys) {
|
|
157
|
-
const localizedText = getProperty(localized, key);
|
|
158
|
-
assertText(localizedText, `${path}:${line} ${code}.${key}`);
|
|
159
|
-
if (placeholderKeys.has(`${path}:${key}`)) {
|
|
160
|
-
assert.equal(localizedText.text.includes('?'), true, `${path}:${line} ${code}.${key} must retain its ? placeholder`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const taskConfig = JSON.parse(await readFile(new URL('../dist/app/task/config.json', import.meta.url), 'utf8'));
|
|
167
|
-
assert.deepEqual(Object.values(taskConfig.locales), codes, 'task app config must load all 16 locales in registry order');
|
|
168
|
-
for (const code of codes) {
|
|
169
|
-
assert.equal(taskConfig.locales[`/package/locale/${code}`], code, `task app must map /package/locale/${code}`);
|
|
170
|
-
}
|
|
171
|
-
const englishTaskLocale = JSON.parse(await readFile(new URL('../dist/app/task/locale/en.json', import.meta.url), 'utf8'));
|
|
172
|
-
const taskKeys = Object.keys(englishTaskLocale).sort();
|
|
173
|
-
for (const code of codes) {
|
|
174
|
-
const locale = JSON.parse(await readFile(new URL(`../dist/app/task/locale/${code}.json`, import.meta.url), 'utf8'));
|
|
175
|
-
assert.deepEqual(Object.keys(locale).sort(), taskKeys, `task app ${code}.json keys must match en.json`);
|
|
176
|
-
for (const [key, value] of Object.entries(locale)) {
|
|
177
|
-
assert.equal(typeof value, 'string', `task app ${code}.json ${key} must be a string`);
|
|
178
|
-
assert.notEqual(value.trim(), '', `task app ${code}.json ${key} must not be empty`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const demoTaskLayout = await readFile(new URL('../dist/app/demo/form/method/task/task.xml', import.meta.url), 'utf8');
|
|
183
|
-
const demoLocaleData = demoTaskLayout.match(/<label>Change global locale lang:<\/label>\s*<layout[^>]*>\s*<select[^>]*:data="\[([^"]+)\]"/)?.[1];
|
|
184
|
-
assert.ok(demoLocaleData, 'demo Library task form must retain the global locale selector');
|
|
185
|
-
const demoLocaleCodes = [...demoLocaleData.matchAll(/'([^']+)'/g)].map((match) => match[1]);
|
|
186
|
-
assert.deepEqual(demoLocaleCodes, codes, 'demo global locale selector must expose all 16 locales in registry order');
|
|
187
|
-
|
|
188
|
-
dom.window.close();
|
|
189
|
-
console.log(`Locale registry, ${tables.length} TypeScript tables, 16 task app locale JSON files and the Demo selector are complete.`);
|
package/test/pop-max-size.mjs
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// Run: node --experimental-vm-modules test/pop-max-size.mjs
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import { SourceTextModule, SyntheticModule } from 'node:vm';
|
|
5
|
-
import { JSDOM } from 'jsdom';
|
|
6
|
-
import ts from 'typescript';
|
|
7
|
-
|
|
8
|
-
const dom = new JSDOM('<!doctype html><style>.pop { max-width: 480px; max-height: 300px; }</style><div id="trigger"></div><div class="pop"></div>');
|
|
9
|
-
for (const name of ['window', 'document', 'Element', 'HTMLElement', 'PointerEvent']) {
|
|
10
|
-
Object.defineProperty(globalThis, name, {
|
|
11
|
-
'configurable': true,
|
|
12
|
-
'value': dom.window[name],
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
globalThis.getComputedStyle = dom.window.getComputedStyle.bind(dom.window);
|
|
16
|
-
Object.defineProperty(window, 'innerWidth', { 'configurable': true, 'value': 1024 });
|
|
17
|
-
Object.defineProperty(window, 'innerHeight', { 'configurable': true, 'writable': true, 'value': 768 });
|
|
18
|
-
|
|
19
|
-
let refreshSize;
|
|
20
|
-
const domModule = new SyntheticModule(['findParentByData', 'watchSize'], function() {
|
|
21
|
-
this.setExport('findParentByData', () => null);
|
|
22
|
-
this.setExport('watchSize', (_id, _pop, callback) => { refreshSize = callback; });
|
|
23
|
-
});
|
|
24
|
-
const toolModule = new SyntheticModule(['sleep'], function() {
|
|
25
|
-
this.setExport('sleep', () => Promise.resolve());
|
|
26
|
-
});
|
|
27
|
-
const empty = new SyntheticModule([], function() {});
|
|
28
|
-
const source = await readFile(new URL('../dist/lib/form.ts', import.meta.url), 'utf8');
|
|
29
|
-
const formModule = new SourceTextModule(ts.transpileModule(source, {
|
|
30
|
-
'compilerOptions': {
|
|
31
|
-
'target': ts.ScriptTarget.ES2022,
|
|
32
|
-
'module': ts.ModuleKind.ESNext,
|
|
33
|
-
},
|
|
34
|
-
}).outputText);
|
|
35
|
-
await formModule.link((specifier) => {
|
|
36
|
-
if (specifier === './dom') {
|
|
37
|
-
return domModule;
|
|
38
|
-
}
|
|
39
|
-
if (specifier === './tool') {
|
|
40
|
-
return toolModule;
|
|
41
|
-
}
|
|
42
|
-
return empty;
|
|
43
|
-
});
|
|
44
|
-
await formModule.evaluate();
|
|
45
|
-
|
|
46
|
-
const trigger = document.querySelector('#trigger');
|
|
47
|
-
const pop = document.querySelector('.pop');
|
|
48
|
-
formModule.namespace.showPop(trigger, pop, 'v', { 'autoPosition': true });
|
|
49
|
-
await Promise.resolve();
|
|
50
|
-
assert.equal(pop.style.maxHeight, 'min(300px, 760px)', 'CSS max-height must survive the viewport limit');
|
|
51
|
-
assert.equal(pop.style.maxWidth, 'min(480px, 1016px)', 'CSS max-width must survive the viewport limit');
|
|
52
|
-
|
|
53
|
-
window.innerHeight = 240;
|
|
54
|
-
refreshSize();
|
|
55
|
-
assert.equal(pop.style.maxHeight, 'min(300px, 232px)', 'a smaller viewport must still constrain the popup');
|
|
56
|
-
|
|
57
|
-
pop.style.maxHeight = '180px';
|
|
58
|
-
refreshSize();
|
|
59
|
-
assert.equal(pop.style.maxHeight, 'min(180px, 232px)', 'an explicit inline limit must take effect');
|
|
60
|
-
|
|
61
|
-
pop.style.maxHeight = '';
|
|
62
|
-
refreshSize();
|
|
63
|
-
assert.equal(pop.style.maxHeight, 'min(300px, 232px)', 'removing an inline limit must restore the CSS limit');
|
|
64
|
-
|
|
65
|
-
dom.window.close();
|
|
66
|
-
console.log('Popup CSS limits, viewport limits, and inline overrides passed.');
|