@canopy-iiif/app 0.7.17 → 0.7.18
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/lib/build/dev.js +204 -4
- package/lib/orchestrator.js +203 -0
- package/lib/search/command-runtime.js +5 -4
- package/lib/search/search-app.jsx +548 -111
- package/package.json +9 -3
- package/types/orchestrator.d.ts +18 -0
- package/ui/dist/index.mjs +236 -42
- package/ui/dist/index.mjs.map +4 -4
- package/ui/dist/server.mjs +55 -4
- package/ui/dist/server.mjs.map +4 -4
- package/ui/styles/components/_command.scss +1 -0
- package/ui/styles/components/_hero.scss +7 -8
- package/ui/styles/index.css +7 -8
package/lib/build/dev.js
CHANGED
|
@@ -2,7 +2,6 @@ const fs = require("fs");
|
|
|
2
2
|
const fsp = fs.promises;
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const { spawn, spawnSync } = require("child_process");
|
|
5
|
-
const { build } = require("../build/build");
|
|
6
5
|
const http = require("http");
|
|
7
6
|
const url = require("url");
|
|
8
7
|
const {
|
|
@@ -22,11 +21,29 @@ function resolveTailwindCli() {
|
|
|
22
21
|
return { cmd: 'tailwindcss', args: [] };
|
|
23
22
|
}
|
|
24
23
|
const PORT = Number(process.env.PORT || 5001);
|
|
24
|
+
const BUILD_MODULE_PATH = path.resolve(__dirname, "build.js");
|
|
25
25
|
let onBuildSuccess = () => {};
|
|
26
26
|
let onBuildStart = () => {};
|
|
27
27
|
let onCssChange = () => {};
|
|
28
28
|
let nextBuildSkipIiif = false; // hint set by watchers
|
|
29
29
|
const UI_DIST_DIR = path.resolve(path.join(__dirname, "../../ui/dist"));
|
|
30
|
+
const APP_PACKAGE_ROOT = path.resolve(path.join(__dirname, "..", ".."));
|
|
31
|
+
const APP_LIB_DIR = path.join(APP_PACKAGE_ROOT, "lib");
|
|
32
|
+
const APP_UI_DIR = path.join(APP_PACKAGE_ROOT, "ui");
|
|
33
|
+
const APP_WATCH_TARGETS = [
|
|
34
|
+
{ dir: APP_LIB_DIR, label: "@canopy-iiif/app/lib" },
|
|
35
|
+
{ dir: APP_UI_DIR, label: "@canopy-iiif/app/ui" },
|
|
36
|
+
];
|
|
37
|
+
const HAS_APP_WORKSPACE = (() => {
|
|
38
|
+
try {
|
|
39
|
+
return fs.existsSync(path.join(APP_PACKAGE_ROOT, "package.json"));
|
|
40
|
+
} catch (_) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
let pendingModuleReload = false;
|
|
45
|
+
let building = false;
|
|
46
|
+
let buildAgain = false;
|
|
30
47
|
|
|
31
48
|
function prettyPath(p) {
|
|
32
49
|
try {
|
|
@@ -40,16 +57,71 @@ function prettyPath(p) {
|
|
|
40
57
|
}
|
|
41
58
|
}
|
|
42
59
|
|
|
60
|
+
function loadBuildFunction() {
|
|
61
|
+
let mod = null;
|
|
62
|
+
try {
|
|
63
|
+
mod = require(BUILD_MODULE_PATH);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`[watch] Failed to load build module (${BUILD_MODULE_PATH}): ${
|
|
67
|
+
error && error.message ? error.message : error
|
|
68
|
+
}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const fn =
|
|
72
|
+
mod && typeof mod.build === "function"
|
|
73
|
+
? mod.build
|
|
74
|
+
: mod && mod.default && typeof mod.default.build === "function"
|
|
75
|
+
? mod.default.build
|
|
76
|
+
: null;
|
|
77
|
+
if (typeof fn !== "function") {
|
|
78
|
+
throw new Error("[watch] Invalid build module export: expected build() function");
|
|
79
|
+
}
|
|
80
|
+
return fn;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function clearAppModuleCache() {
|
|
84
|
+
try {
|
|
85
|
+
const prefix = APP_PACKAGE_ROOT.endsWith(path.sep)
|
|
86
|
+
? APP_PACKAGE_ROOT
|
|
87
|
+
: APP_PACKAGE_ROOT + path.sep;
|
|
88
|
+
for (const key of Object.keys(require.cache || {})) {
|
|
89
|
+
if (!key) continue;
|
|
90
|
+
try {
|
|
91
|
+
if (key === APP_PACKAGE_ROOT || key.startsWith(prefix)) {
|
|
92
|
+
delete require.cache[key];
|
|
93
|
+
}
|
|
94
|
+
} catch (_) {}
|
|
95
|
+
}
|
|
96
|
+
} catch (_) {}
|
|
97
|
+
}
|
|
98
|
+
|
|
43
99
|
async function runBuild() {
|
|
100
|
+
if (building) {
|
|
101
|
+
buildAgain = true;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
building = true;
|
|
105
|
+
const hint = { skipIiif: !!nextBuildSkipIiif };
|
|
106
|
+
nextBuildSkipIiif = false;
|
|
44
107
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
108
|
+
if (pendingModuleReload) {
|
|
109
|
+
clearAppModuleCache();
|
|
110
|
+
pendingModuleReload = false;
|
|
111
|
+
}
|
|
112
|
+
const buildFn = loadBuildFunction();
|
|
113
|
+
await buildFn(hint);
|
|
48
114
|
try {
|
|
49
115
|
onBuildSuccess();
|
|
50
116
|
} catch (_) {}
|
|
51
117
|
} catch (e) {
|
|
52
118
|
console.error("Build failed:", e && e.message ? e.message : e);
|
|
119
|
+
} finally {
|
|
120
|
+
building = false;
|
|
121
|
+
if (buildAgain) {
|
|
122
|
+
buildAgain = false;
|
|
123
|
+
debounceBuild();
|
|
124
|
+
}
|
|
53
125
|
}
|
|
54
126
|
}
|
|
55
127
|
|
|
@@ -334,6 +406,131 @@ function watchUiDistPerDir() {
|
|
|
334
406
|
};
|
|
335
407
|
}
|
|
336
408
|
|
|
409
|
+
const APP_WATCH_EXTENSIONS = new Set([".js", ".jsx", ".scss"]);
|
|
410
|
+
|
|
411
|
+
function shouldIgnoreAppSourcePath(p) {
|
|
412
|
+
try {
|
|
413
|
+
const resolved = path.resolve(p);
|
|
414
|
+
const rel = path.relative(APP_PACKAGE_ROOT, resolved);
|
|
415
|
+
if (!rel || rel === "") return false;
|
|
416
|
+
if (rel.startsWith("..")) return true;
|
|
417
|
+
const segments = rel.split(path.sep).filter(Boolean);
|
|
418
|
+
if (!segments.length) return false;
|
|
419
|
+
if (segments.includes("node_modules")) return true;
|
|
420
|
+
if (segments.includes(".git")) return true;
|
|
421
|
+
if (segments[0] === "ui" && segments[1] === "dist") return true;
|
|
422
|
+
return false;
|
|
423
|
+
} catch (_) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function handleAppSourceChange(baseDir, eventType, filename, label) {
|
|
429
|
+
if (!filename) return;
|
|
430
|
+
const full = path.resolve(baseDir, filename);
|
|
431
|
+
if (shouldIgnoreAppSourcePath(full)) return;
|
|
432
|
+
const ext = path.extname(full).toLowerCase();
|
|
433
|
+
if (!APP_WATCH_EXTENSIONS.has(ext)) return;
|
|
434
|
+
try {
|
|
435
|
+
const relLib = path.relative(APP_LIB_DIR, full);
|
|
436
|
+
if (!relLib.startsWith("..") && !path.isAbsolute(relLib)) {
|
|
437
|
+
pendingModuleReload = true;
|
|
438
|
+
}
|
|
439
|
+
} catch (_) {}
|
|
440
|
+
try {
|
|
441
|
+
console.log(
|
|
442
|
+
`[pkg] ${eventType}: ${prettyPath(full)}${label ? ` (${label})` : ""}`
|
|
443
|
+
);
|
|
444
|
+
} catch (_) {}
|
|
445
|
+
nextBuildSkipIiif = true;
|
|
446
|
+
try {
|
|
447
|
+
onBuildStart();
|
|
448
|
+
} catch (_) {}
|
|
449
|
+
debounceBuild();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function tryRecursiveWatchAppDir(dir, label) {
|
|
453
|
+
try {
|
|
454
|
+
return fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
455
|
+
handleAppSourceChange(dir, eventType, filename, label);
|
|
456
|
+
});
|
|
457
|
+
} catch (_) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function watchAppDirPerDir(dir, label) {
|
|
463
|
+
const watchers = new Map();
|
|
464
|
+
|
|
465
|
+
function watchDir(target) {
|
|
466
|
+
if (watchers.has(target)) return;
|
|
467
|
+
if (shouldIgnoreAppSourcePath(target)) return;
|
|
468
|
+
try {
|
|
469
|
+
const w = fs.watch(target, (eventType, filename) => {
|
|
470
|
+
if (filename) {
|
|
471
|
+
handleAppSourceChange(target, eventType, filename, label);
|
|
472
|
+
}
|
|
473
|
+
scan(target);
|
|
474
|
+
});
|
|
475
|
+
watchers.set(target, w);
|
|
476
|
+
} catch (_) {}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function scan(target) {
|
|
480
|
+
let entries;
|
|
481
|
+
try {
|
|
482
|
+
entries = fs.readdirSync(target, { withFileTypes: true });
|
|
483
|
+
} catch (_) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
for (const entry of entries) {
|
|
487
|
+
if (!entry.isDirectory()) continue;
|
|
488
|
+
const sub = path.join(target, entry.name);
|
|
489
|
+
if (shouldIgnoreAppSourcePath(sub)) continue;
|
|
490
|
+
watchDir(sub);
|
|
491
|
+
scan(sub);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
watchDir(dir);
|
|
496
|
+
scan(dir);
|
|
497
|
+
|
|
498
|
+
return () => {
|
|
499
|
+
for (const w of watchers.values()) {
|
|
500
|
+
try {
|
|
501
|
+
w.close();
|
|
502
|
+
} catch (_) {}
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function watchAppSources() {
|
|
508
|
+
if (!HAS_APP_WORKSPACE) return () => {};
|
|
509
|
+
const stops = [];
|
|
510
|
+
for (const target of APP_WATCH_TARGETS) {
|
|
511
|
+
const { dir, label } = target;
|
|
512
|
+
if (!dir || !fs.existsSync(dir)) continue;
|
|
513
|
+
console.log(`[Watching] ${prettyPath(dir)} (${label})`);
|
|
514
|
+
const watcher = tryRecursiveWatchAppDir(dir, label);
|
|
515
|
+
if (!watcher) {
|
|
516
|
+
stops.push(watchAppDirPerDir(dir, label));
|
|
517
|
+
} else {
|
|
518
|
+
stops.push(() => {
|
|
519
|
+
try {
|
|
520
|
+
watcher.close();
|
|
521
|
+
} catch (_) {}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return () => {
|
|
526
|
+
for (const stop of stops) {
|
|
527
|
+
try {
|
|
528
|
+
if (typeof stop === "function") stop();
|
|
529
|
+
} catch (_) {}
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
337
534
|
const MIME = {
|
|
338
535
|
".html": "text/html; charset=utf-8",
|
|
339
536
|
".css": "text/css; charset=utf-8",
|
|
@@ -900,6 +1097,9 @@ async function dev() {
|
|
|
900
1097
|
const urw = tryRecursiveWatchUiDist();
|
|
901
1098
|
if (!urw) watchUiDistPerDir();
|
|
902
1099
|
}
|
|
1100
|
+
if (HAS_APP_WORKSPACE) {
|
|
1101
|
+
watchAppSources();
|
|
1102
|
+
}
|
|
903
1103
|
}
|
|
904
1104
|
|
|
905
1105
|
module.exports = { dev };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const log = (msg) => console.log(`[canopy] ${msg}`);
|
|
6
|
+
const warn = (msg) => console.warn(`[canopy][warn] ${msg}`);
|
|
7
|
+
const err = (msg) => console.error(`[canopy][error] ${msg}`);
|
|
8
|
+
|
|
9
|
+
let uiWatcherChild = null;
|
|
10
|
+
|
|
11
|
+
const workspacePackageJsonPath = path.resolve(process.cwd(), 'packages/app/package.json');
|
|
12
|
+
const hasAppWorkspace = fs.existsSync(workspacePackageJsonPath);
|
|
13
|
+
|
|
14
|
+
function getMode(argv = process.argv.slice(2), env = process.env) {
|
|
15
|
+
const cli = new Set(argv);
|
|
16
|
+
if (cli.has('--dev')) return 'dev';
|
|
17
|
+
if (cli.has('--build')) return 'build';
|
|
18
|
+
|
|
19
|
+
if (env.CANOPY_MODE === 'dev') return 'dev';
|
|
20
|
+
if (env.CANOPY_MODE === 'build') return 'build';
|
|
21
|
+
|
|
22
|
+
const npmScript = env.npm_lifecycle_event;
|
|
23
|
+
if (npmScript === 'dev') return 'dev';
|
|
24
|
+
if (npmScript === 'build') return 'build';
|
|
25
|
+
|
|
26
|
+
return 'build';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runOnce(cmd, args, opts = {}) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const child = spawn(cmd, args, { stdio: 'inherit', shell: false, ...opts });
|
|
32
|
+
child.on('error', reject);
|
|
33
|
+
child.on('exit', (code) => {
|
|
34
|
+
if (code === 0) {
|
|
35
|
+
resolve();
|
|
36
|
+
} else {
|
|
37
|
+
reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function start(cmd, args, opts = {}) {
|
|
44
|
+
const child = spawn(cmd, args, { stdio: 'inherit', shell: false, ...opts });
|
|
45
|
+
child.on('error', (error) => {
|
|
46
|
+
const message = error && error.message ? error.message : String(error);
|
|
47
|
+
warn(`Subprocess error (${cmd}): ${message}`);
|
|
48
|
+
});
|
|
49
|
+
return child;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function prepareUi(mode, env = process.env) {
|
|
53
|
+
if (!hasAppWorkspace) {
|
|
54
|
+
log('Using bundled UI assets from @canopy-iiif/app (workspace not detected)');
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (mode === 'build') {
|
|
59
|
+
log('Building UI assets (@canopy-iiif/app/ui)');
|
|
60
|
+
try {
|
|
61
|
+
await runOnce('npm', ['-w', '@canopy-iiif/app', 'run', 'ui:build'], { env });
|
|
62
|
+
log('UI assets built');
|
|
63
|
+
} catch (error) {
|
|
64
|
+
warn(`UI build skipped: ${(error && error.message) || String(error)}`);
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
log('Prebuilding UI assets (@canopy-iiif/app/ui)');
|
|
71
|
+
await runOnce('npm', ['-w', '@canopy-iiif/app', 'run', 'ui:build'], { env });
|
|
72
|
+
} catch (error) {
|
|
73
|
+
warn(`UI prebuild skipped: ${(error && error.message) || String(error)}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
log('Starting UI watcher (@canopy-iiif/app/ui)');
|
|
77
|
+
try {
|
|
78
|
+
uiWatcherChild = start('npm', ['-w', '@canopy-iiif/app', 'run', 'ui:watch'], { env });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
warn(`UI watch skipped: ${(error && error.message) || String(error)}`);
|
|
81
|
+
uiWatcherChild = null;
|
|
82
|
+
}
|
|
83
|
+
return uiWatcherChild;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function loadLibraryApi() {
|
|
87
|
+
let lib;
|
|
88
|
+
try {
|
|
89
|
+
lib = require('./index.js');
|
|
90
|
+
} catch (e) {
|
|
91
|
+
const hint = [
|
|
92
|
+
'Unable to load @canopy-iiif/app.',
|
|
93
|
+
'Ensure dependencies are installed (npm install)',
|
|
94
|
+
"and that peer deps like 'react' are present.",
|
|
95
|
+
].join(' ');
|
|
96
|
+
const detail = e && e.message ? `\nCaused by: ${e.message}` : '';
|
|
97
|
+
throw new Error(`${hint}${detail}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const api = lib && (typeof lib.build === 'function' || typeof lib.dev === 'function')
|
|
101
|
+
? lib
|
|
102
|
+
: lib && lib.default
|
|
103
|
+
? lib.default
|
|
104
|
+
: lib;
|
|
105
|
+
|
|
106
|
+
if (!api || (typeof api.build !== 'function' && typeof api.dev !== 'function')) {
|
|
107
|
+
throw new TypeError('Invalid @canopy-iiif/app export: expected functions build() and/or dev().');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return api;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function attachSignalHandlers() {
|
|
114
|
+
const clean = () => {
|
|
115
|
+
if (uiWatcherChild && !uiWatcherChild.killed) {
|
|
116
|
+
try { uiWatcherChild.kill(); } catch (_) {}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
process.on('SIGINT', () => {
|
|
121
|
+
clean();
|
|
122
|
+
process.exit(130);
|
|
123
|
+
});
|
|
124
|
+
process.on('SIGTERM', () => {
|
|
125
|
+
clean();
|
|
126
|
+
process.exit(143);
|
|
127
|
+
});
|
|
128
|
+
process.on('exit', clean);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function verifyBuildOutput(outDir = 'site') {
|
|
132
|
+
const root = path.resolve(outDir);
|
|
133
|
+
function walk(dir) {
|
|
134
|
+
let count = 0;
|
|
135
|
+
if (!fs.existsSync(dir)) return 0;
|
|
136
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
const p = path.join(dir, entry.name);
|
|
139
|
+
if (entry.isDirectory()) count += walk(p);
|
|
140
|
+
else if (entry.isFile() && p.toLowerCase().endsWith('.html')) count += 1;
|
|
141
|
+
}
|
|
142
|
+
return count;
|
|
143
|
+
}
|
|
144
|
+
const pages = walk(root);
|
|
145
|
+
if (!pages) {
|
|
146
|
+
throw new Error('CI check failed: no HTML pages generated in "site/".');
|
|
147
|
+
}
|
|
148
|
+
log(`CI check: found ${pages} HTML page(s) in ${root}.`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function orchestrate(options = {}) {
|
|
152
|
+
const argv = options.argv || process.argv.slice(2);
|
|
153
|
+
const env = options.env || process.env;
|
|
154
|
+
|
|
155
|
+
process.title = 'canopy-app';
|
|
156
|
+
const mode = getMode(argv, env);
|
|
157
|
+
log(`Mode: ${mode}`);
|
|
158
|
+
|
|
159
|
+
const cli = new Set(argv);
|
|
160
|
+
if (cli.has('--verify')) {
|
|
161
|
+
verifyBuildOutput(env.CANOPY_OUT_DIR || 'site');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await prepareUi(mode, env);
|
|
166
|
+
|
|
167
|
+
const api = loadLibraryApi();
|
|
168
|
+
try {
|
|
169
|
+
if (mode === 'dev') {
|
|
170
|
+
attachSignalHandlers();
|
|
171
|
+
log('Starting dev server...');
|
|
172
|
+
await (typeof api.dev === 'function' ? api.dev() : Promise.resolve());
|
|
173
|
+
} else {
|
|
174
|
+
log('Building site...');
|
|
175
|
+
if (typeof api.build === 'function') {
|
|
176
|
+
await api.build();
|
|
177
|
+
}
|
|
178
|
+
log('Build complete');
|
|
179
|
+
if (env.CANOPY_VERIFY === '1' || env.CANOPY_VERIFY === 'true') {
|
|
180
|
+
verifyBuildOutput(env.CANOPY_OUT_DIR || 'site');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} finally {
|
|
184
|
+
if (uiWatcherChild && !uiWatcherChild.killed) {
|
|
185
|
+
try { uiWatcherChild.kill(); } catch (_) {}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
orchestrate,
|
|
192
|
+
verifyBuildOutput,
|
|
193
|
+
_internals: {
|
|
194
|
+
getMode,
|
|
195
|
+
prepareUi,
|
|
196
|
+
loadLibraryApi,
|
|
197
|
+
runOnce,
|
|
198
|
+
start,
|
|
199
|
+
},
|
|
200
|
+
log,
|
|
201
|
+
warn,
|
|
202
|
+
err,
|
|
203
|
+
};
|
|
@@ -346,10 +346,11 @@ async function attachCommand(host) {
|
|
|
346
346
|
|
|
347
347
|
host.addEventListener('click', (event) => {
|
|
348
348
|
const trigger = event.target && event.target.closest && event.target.closest('[data-canopy-command-trigger]');
|
|
349
|
-
if (trigger)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
if (!trigger) return;
|
|
350
|
+
const mode = (trigger.dataset && trigger.dataset.canopyCommandTrigger) || '';
|
|
351
|
+
if (mode === 'submit' || mode === 'form') return;
|
|
352
|
+
event.preventDefault();
|
|
353
|
+
openPanel();
|
|
353
354
|
});
|
|
354
355
|
|
|
355
356
|
try {
|