arcane-os 0.3.0 → 0.3.2
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/CHANGELOG.md +27 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +887 -909
- package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
- package/browser-runtime/ai/browser-wasm.mjs +24 -35
- package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
- package/browser-runtime/ai/model-controller.mjs +584 -181
- package/browser-runtime/ai/speech-worker-client.mjs +8 -146
- package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
- package/browser-runtime/dom-event-instrumentation.mjs +55 -147
- package/browser-runtime/event-manager.mjs +239 -624
- package/package.json +5 -6
- package/runtime/arcane/components/app-bar.html +3 -15
- package/runtime/arcane/components/assistant-panel.html +10 -10
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chat.html +1359 -135
- package/runtime/arcane/components/conversation-view.html +2 -2
- package/runtime/arcane/components/document-inspector.html +11 -17
- package/runtime/arcane/components/file-manager.html +13 -56
- package/runtime/arcane/components/markdown-document.html +82 -281
- package/runtime/arcane/components/markdown-editor.html +7 -10
- package/runtime/arcane/components/media-embed.html +6 -6
- package/runtime/arcane/components/screen-capture.html +4 -4
- package/runtime/arcane/components/source-explanation.html +2 -2
- package/runtime/arcane/components/speech.html +112 -68
- package/runtime/arcane/components/terminal-workspace.html +4 -4
- package/runtime/arcane/components/theme-editor.html +1 -1
- package/runtime/arcane/components/unified-inbox.html +2 -2
- package/runtime/arcane/components/voice-transcription.html +31 -21
- package/runtime/arcane/entities/Calculation.js +2 -3
- package/runtime/arcane/entities/Chat.js +228 -43
- package/runtime/arcane/entities/Preference.js +3 -5
- package/runtime/arcane/entities/Weather.js +5 -5
- package/runtime/arcane/modules/AI.js +1050 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
- package/runtime/arcane/modules/AIResponseLength.js +9 -19
- package/runtime/arcane/modules/AIRuntimeState.js +109 -72
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
- package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
- package/runtime/arcane/modules/CalculatorEngine.js +9 -9
- package/runtime/arcane/modules/CommunicationAppController.js +3 -7
- package/runtime/arcane/modules/ComponentContracts.js +30 -32
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
- package/runtime/arcane/modules/ConversationActionItems.js +26 -59
- package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
- package/runtime/arcane/modules/ConversationTimebox.js +27 -15
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
- package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
- package/runtime/arcane/modules/HTMLImport.js +54 -1
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
- package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
- package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
- package/runtime/arcane/modules/MD.js +1 -45
- package/runtime/arcane/modules/Mail.js +51 -103
- package/runtime/arcane/modules/MailOutbox.mjs +95 -193
- package/runtime/arcane/modules/MailTransport.mjs +36 -57
- package/runtime/arcane/modules/ModelDefinition.js +22 -106
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
- package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
- package/runtime/arcane/modules/PreferenceStore.js +102 -30
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
- package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
- package/runtime/arcane/modules/ScreenCapture.js +175 -128
- package/runtime/arcane/modules/SpeechPlayback.js +46 -149
- package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
- package/runtime/arcane/modules/ToolCallRouter.js +25 -12
- package/runtime/arcane/modules/YouTubeMedia.js +6 -5
- package/schemas/arcane-app-bundle.schema.json +13 -78
- package/schemas/arcane-app.schema.json +9 -25
- package/schemas/arcane-lock.schema.json +18 -151
- package/schemas/arcane-package.schema.json +2 -16
- package/schemas/native-build-plan.schema.json +119 -122
- package/src/app-descriptor.mjs +75 -132
- package/src/application-tests.mjs +200 -0
- package/src/cli/main.mjs +27 -46
- package/src/constants.mjs +3 -4
- package/src/dev-server.mjs +30 -324
- package/src/doctor.mjs +92 -154
- package/src/dom-event-instrumentation.mjs +55 -147
- package/src/errors.mjs +2 -3
- package/src/event-manager.mjs +239 -624
- package/src/event-queue.mjs +3 -3
- package/src/import-map.mjs +273 -1028
- package/src/index.mjs +14 -16
- package/src/installed-sdk-runtime.mjs +40 -62
- package/src/integrated-provider-loader.mjs +53 -382
- package/src/mail-api.mjs +0 -2
- package/src/mail-server.mjs +224 -580
- package/src/mail.mjs +4 -10
- package/src/native-plan.mjs +163 -598
- package/src/native-provider-loader.mjs +104 -1063
- package/src/packager/core.mjs +485 -3229
- package/src/process.mjs +5 -10
- package/src/release-bundle.mjs +292 -2405
- package/src/runtime.mjs +76 -396
- package/src/scaffold.mjs +30 -80
- package/src/sdk-browser-runtime.mjs +70 -626
- package/src/source-server.mjs +588 -0
- package/src/targets/index.mjs +78 -188
- package/src/templates/workspace-template.mjs +19 -135
- package/src/testing-loader.mjs +164 -0
- package/src/testing.mjs +1 -1
- package/src/toolchain.mjs +131 -544
- package/src/update-check.mjs +26 -64
- package/src/workspace-operation-lock.mjs +139 -430
- package/src/workspace-runtime.mjs +112 -779
- package/src/workspace.mjs +40 -302
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
- package/browser-runtime/ai/internal/sha256.mjs +0 -166
- package/docs/architecture.md +0 -344
- package/docs/compatibility.md +0 -36
- package/docs/event-manager.md +0 -294
- package/docs/platform-targets.md +0 -108
- package/docs/publishing.md +0 -201
- package/docs/reference/README.md +0 -187
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1252
- package/docs/reference/ai/browser-wasm.md +0 -530
- package/docs/reference/arcane-ollama.md +0 -288
- package/docs/reference/availability-and-normalization.md +0 -183
- package/docs/reference/behavioral-testing.md +0 -133
- package/docs/reference/cli.md +0 -779
- package/docs/reference/core/README.md +0 -62
- package/docs/reference/core/arcane-ai-contracts.md +0 -907
- package/docs/reference/core/arcane-api.md +0 -601
- package/docs/reference/core/arcane-entities.md +0 -65
- package/docs/reference/core/arcane-events.md +0 -134
- package/docs/reference/core/ollama-module.md +0 -181
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
- package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
- package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
- package/docs/reference/event-manager.md +0 -1511
- package/docs/reference/inventory/package-api.json +0 -3284
- package/docs/reference/inventory/runtime-components.json +0 -1011
- package/docs/reference/inventory/runtime-entities.json +0 -26
- package/docs/reference/inventory/runtime-modules.json +0 -1431
- package/docs/reference/mail.md +0 -316
- package/docs/reference/protocols.md +0 -677
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2960
- package/docs/reference/sdk-api.md +0 -6694
- package/docs/roadmap.md +0 -79
- package/docs/work-amplification.md +0 -129
- package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
|
@@ -232,47 +232,6 @@
|
|
|
232
232
|
const contentElement=host.shadowRoot.querySelector('#content');
|
|
233
233
|
|
|
234
234
|
// BEGIN MARKDOWN_DOCUMENT_POLICY
|
|
235
|
-
const MARKDOWN_DOCUMENT_ALLOWED_ELEMENTS=Object.freeze([
|
|
236
|
-
'A','ABBR','BLOCKQUOTE','BR','CODE','DEL','EM',
|
|
237
|
-
'H1','H2','H3','H4','H5','H6','HR','IMG','KBD','LI',
|
|
238
|
-
'OL','P','PRE','S','STRONG','SUB','SUP','TABLE','TBODY',
|
|
239
|
-
'TD','TFOOT','TH','THEAD','TR','UL'
|
|
240
|
-
]);
|
|
241
|
-
const MARKDOWN_DOCUMENT_ALLOWED_ATTRIBUTES=Object.freeze(
|
|
242
|
-
{
|
|
243
|
-
A:Object.freeze(['href','title']),
|
|
244
|
-
ABBR:Object.freeze(['title']),
|
|
245
|
-
CODE:Object.freeze(['class']),
|
|
246
|
-
IMG:Object.freeze(['alt','src','title']),
|
|
247
|
-
LI:Object.freeze(['value']),
|
|
248
|
-
OL:Object.freeze(['start']),
|
|
249
|
-
TD:Object.freeze(['colspan','rowspan']),
|
|
250
|
-
TH:Object.freeze(['colspan','rowspan','scope'])
|
|
251
|
-
}
|
|
252
|
-
);
|
|
253
|
-
const MARKDOWN_DOCUMENT_LINK_PROTOCOLS=new Set(['http:','https:','mailto:']);
|
|
254
|
-
const MARKDOWN_DOCUMENT_ASSET_PROTOCOLS=new Set(['http:','https:']);
|
|
255
|
-
|
|
256
|
-
function markdownDocumentElementAllowed(name=''){
|
|
257
|
-
return MARKDOWN_DOCUMENT_ALLOWED_ELEMENTS.includes(
|
|
258
|
-
String(name||'').toUpperCase()
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function markdownDocumentAllowedAttributes(name=''){
|
|
263
|
-
return MARKDOWN_DOCUMENT_ALLOWED_ATTRIBUTES[
|
|
264
|
-
String(name||'').toUpperCase()
|
|
265
|
-
]||Object.freeze([]);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function markdownDocumentCleanURLInput(value=''){
|
|
269
|
-
const input=String(value??'').trim();
|
|
270
|
-
if(!input||/[\u0000-\u001F\u007F]/.test(input)){
|
|
271
|
-
return '';
|
|
272
|
-
}
|
|
273
|
-
return input;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
235
|
function markdownDocumentDecodeFragment(value=''){
|
|
277
236
|
try{
|
|
278
237
|
return decodeURIComponent(String(value||''));
|
|
@@ -281,106 +240,58 @@
|
|
|
281
240
|
}
|
|
282
241
|
}
|
|
283
242
|
|
|
284
|
-
function markdownDocumentSourceURL(value='',baseURL=''
|
|
243
|
+
function markdownDocumentSourceURL(value='',baseURL=''){
|
|
285
244
|
const fallback=baseURL||document.baseURI;
|
|
286
|
-
const expectedOrigin=pageOrigin
|
|
287
|
-
||globalThis.location?.origin
|
|
288
|
-
||new URL(fallback).origin;
|
|
289
|
-
const input=markdownDocumentCleanURLInput(value||fallback);
|
|
290
|
-
if(!input){
|
|
291
|
-
return '';
|
|
292
|
-
}
|
|
293
|
-
|
|
294
245
|
try{
|
|
295
|
-
|
|
296
|
-
if(
|
|
297
|
-
!MARKDOWN_DOCUMENT_ASSET_PROTOCOLS.has(url.protocol)
|
|
298
|
-
||url.origin!==expectedOrigin
|
|
299
|
-
||url.username
|
|
300
|
-
||url.password
|
|
301
|
-
){
|
|
302
|
-
return '';
|
|
303
|
-
}
|
|
304
|
-
return url.href;
|
|
246
|
+
return new URL(String(value||fallback),fallback).href;
|
|
305
247
|
}catch{
|
|
306
248
|
return '';
|
|
307
249
|
}
|
|
308
250
|
}
|
|
309
251
|
|
|
310
|
-
function markdownDocumentLinkPolicy(value='',sourceURL=''
|
|
311
|
-
const input=
|
|
252
|
+
function markdownDocumentLinkPolicy(value='',sourceURL=''){
|
|
253
|
+
const input=String(value??'');
|
|
312
254
|
if(!input){
|
|
313
255
|
return null;
|
|
314
256
|
}
|
|
315
257
|
|
|
316
|
-
const expectedOrigin=pageOrigin
|
|
317
|
-
||globalThis.location?.origin
|
|
318
|
-
||new URL(sourceURL).origin;
|
|
319
|
-
|
|
320
258
|
if(input.startsWith('#')){
|
|
321
|
-
const fragment=markdownDocumentDecodeFragment(input.
|
|
322
|
-
.replace(/[\u0000-\u001F\u007F]/g,'')
|
|
323
|
-
.slice(0,256);
|
|
259
|
+
const fragment=markdownDocumentDecodeFragment(input.substring(1));
|
|
324
260
|
if(!fragment){
|
|
325
261
|
return null;
|
|
326
262
|
}
|
|
327
|
-
return
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
);
|
|
263
|
+
return {
|
|
264
|
+
external:false,
|
|
265
|
+
fragment,
|
|
266
|
+
href:input,
|
|
267
|
+
kind:'fragment',
|
|
268
|
+
targetURL:new URL(input,sourceURL).href
|
|
269
|
+
};
|
|
336
270
|
}
|
|
337
271
|
|
|
338
272
|
try{
|
|
339
273
|
const url=new URL(input,sourceURL);
|
|
340
|
-
|
|
341
|
-
!MARKDOWN_DOCUMENT_LINK_PROTOCOLS.has(url.protocol)
|
|
342
|
-
||url.username
|
|
343
|
-
||url.password
|
|
344
|
-
){
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const sameOrigin=url.origin===expectedOrigin;
|
|
274
|
+
const sameOrigin=url.origin===new URL(sourceURL).origin;
|
|
349
275
|
const markdown=sameOrigin&&/\.(?:md|markdown)$/i.test(url.pathname);
|
|
350
|
-
return
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
);
|
|
276
|
+
return {
|
|
277
|
+
external:!sameOrigin,
|
|
278
|
+
fragment:markdownDocumentDecodeFragment(url.hash.substring(1)),
|
|
279
|
+
href:url.href,
|
|
280
|
+
kind:markdown?'markdown':'link',
|
|
281
|
+
targetURL:url.href
|
|
282
|
+
};
|
|
359
283
|
}catch{
|
|
360
284
|
return null;
|
|
361
285
|
}
|
|
362
286
|
}
|
|
363
287
|
|
|
364
|
-
function markdownDocumentAssetURL(value='',sourceURL=''
|
|
365
|
-
const input=
|
|
288
|
+
function markdownDocumentAssetURL(value='',sourceURL=''){
|
|
289
|
+
const input=String(value??'');
|
|
366
290
|
if(!input){
|
|
367
291
|
return '';
|
|
368
292
|
}
|
|
369
|
-
|
|
370
|
-
const expectedOrigin=pageOrigin
|
|
371
|
-
||globalThis.location?.origin
|
|
372
|
-
||new URL(sourceURL).origin;
|
|
373
293
|
try{
|
|
374
|
-
|
|
375
|
-
if(
|
|
376
|
-
!MARKDOWN_DOCUMENT_ASSET_PROTOCOLS.has(url.protocol)
|
|
377
|
-
||url.origin!==expectedOrigin
|
|
378
|
-
||url.username
|
|
379
|
-
||url.password
|
|
380
|
-
){
|
|
381
|
-
return '';
|
|
382
|
-
}
|
|
383
|
-
return url.href;
|
|
294
|
+
return new URL(input,sourceURL).href;
|
|
384
295
|
}catch{
|
|
385
296
|
return '';
|
|
386
297
|
}
|
|
@@ -393,8 +304,7 @@
|
|
|
393
304
|
.toLowerCase()
|
|
394
305
|
.replace(/&/g,' and ')
|
|
395
306
|
.replace(/[^a-z0-9]+/g,'-')
|
|
396
|
-
.replace(/^-+|-+$/g,'')
|
|
397
|
-
.slice(0,80);
|
|
307
|
+
.replace(/^-+|-+$/g,'');
|
|
398
308
|
return identifier||'section';
|
|
399
309
|
}
|
|
400
310
|
|
|
@@ -416,22 +326,18 @@
|
|
|
416
326
|
document.baseURI,
|
|
417
327
|
pageOrigin
|
|
418
328
|
);
|
|
419
|
-
const defaultOptions=
|
|
420
|
-
{
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
showTableOfContents:true,
|
|
432
|
-
sourceURL:defaultSourceURL
|
|
433
|
-
}
|
|
434
|
-
);
|
|
329
|
+
const defaultOptions={
|
|
330
|
+
labels:{
|
|
331
|
+
content:'Document content',
|
|
332
|
+
empty:'No document content is available.',
|
|
333
|
+
error:'Unable to display this document.',
|
|
334
|
+
loading:'Loading document…',
|
|
335
|
+
tableOfContents:'On this page'
|
|
336
|
+
},
|
|
337
|
+
onNavigate:null,
|
|
338
|
+
showTableOfContents:true,
|
|
339
|
+
sourceURL:defaultSourceURL
|
|
340
|
+
};
|
|
435
341
|
|
|
436
342
|
let currentMarkdown='';
|
|
437
343
|
let currentSourceURL=defaultSourceURL;
|
|
@@ -503,7 +409,7 @@
|
|
|
503
409
|
pageOrigin
|
|
504
410
|
);
|
|
505
411
|
if(!sourceURL){
|
|
506
|
-
throw new TypeError('Markdown document sourceURL must resolve to a
|
|
412
|
+
throw new TypeError('Markdown document sourceURL must resolve to a valid URL.');
|
|
507
413
|
}
|
|
508
414
|
|
|
509
415
|
const onNavigate=input.onNavigate===undefined
|
|
@@ -520,14 +426,12 @@
|
|
|
520
426
|
throw new TypeError('Markdown document showTableOfContents must be boolean.');
|
|
521
427
|
}
|
|
522
428
|
|
|
523
|
-
return
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
);
|
|
429
|
+
return {
|
|
430
|
+
labels,
|
|
431
|
+
onNavigate,
|
|
432
|
+
showTableOfContents,
|
|
433
|
+
sourceURL
|
|
434
|
+
};
|
|
531
435
|
}
|
|
532
436
|
|
|
533
437
|
function getOptions(){
|
|
@@ -712,11 +616,10 @@
|
|
|
712
616
|
return true;
|
|
713
617
|
}
|
|
714
618
|
|
|
715
|
-
// MD.safeRendered
|
|
716
|
-
//
|
|
717
|
-
// that the shared sanitizer intentionally leaves available.
|
|
619
|
+
// MD.safeRendered owns Markdown rendering. The detached template keeps
|
|
620
|
+
// source-relative links and images coherent before insertion.
|
|
718
621
|
const safeRendered=new MD(markdown).safeRendered;
|
|
719
|
-
const fragment=
|
|
622
|
+
const fragment=renderedMarkdownFragment(safeRendered,sourceURL);
|
|
720
623
|
const meaningful=Boolean(
|
|
721
624
|
fragment.textContent?.trim()
|
|
722
625
|
||fragment.querySelector('img,hr,table')
|
|
@@ -741,136 +644,38 @@
|
|
|
741
644
|
return true;
|
|
742
645
|
}
|
|
743
646
|
|
|
744
|
-
function
|
|
647
|
+
function renderedMarkdownFragment(html='',sourceURL=options.sourceURL){
|
|
745
648
|
const template=document.createElement('template');
|
|
746
649
|
template.innerHTML=html;
|
|
747
650
|
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
const
|
|
751
|
-
if(!
|
|
752
|
-
// Removing the complete subtree rejects custom elements such as
|
|
753
|
-
// html-import rather than preserving executable descendants.
|
|
754
|
-
element.remove();
|
|
651
|
+
for(const link of template.content.querySelectorAll('a[href]')){
|
|
652
|
+
const originalHref=link.getAttribute('href')||'';
|
|
653
|
+
const policy=markdownDocumentLinkPolicy(originalHref,sourceURL,pageOrigin);
|
|
654
|
+
if(!policy){
|
|
755
655
|
continue;
|
|
756
656
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
657
|
+
link.setAttribute('href',policy.href);
|
|
658
|
+
link.dataset.markdownKind=policy.kind;
|
|
659
|
+
link.dataset.markdownOriginalHref=originalHref;
|
|
660
|
+
link.dataset.markdownTargetUrl=policy.targetURL;
|
|
661
|
+
link.dataset.markdownFragment=policy.fragment;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
for(const image of template.content.querySelectorAll('img[src]')){
|
|
665
|
+
const resolved=markdownDocumentAssetURL(
|
|
666
|
+
image.getAttribute('src')||'',
|
|
667
|
+
sourceURL,
|
|
668
|
+
pageOrigin
|
|
669
|
+
);
|
|
670
|
+
if(resolved){
|
|
671
|
+
image.setAttribute('src',resolved);
|
|
770
672
|
}
|
|
673
|
+
image.alt=image.getAttribute('alt')||'';
|
|
771
674
|
}
|
|
772
675
|
|
|
773
|
-
const comments=[];
|
|
774
|
-
const commentWalker=document.createTreeWalker(
|
|
775
|
-
template.content,
|
|
776
|
-
globalThis.NodeFilter?.SHOW_COMMENT||128
|
|
777
|
-
);
|
|
778
|
-
while(commentWalker.nextNode()){
|
|
779
|
-
comments.push(commentWalker.currentNode);
|
|
780
|
-
}
|
|
781
|
-
comments.forEach(comment=>comment.remove());
|
|
782
676
|
return template.content;
|
|
783
677
|
}
|
|
784
678
|
|
|
785
|
-
function sanitizeAllowedAttributeValues(element,name){
|
|
786
|
-
for(const attribute of Array.from(element.attributes)){
|
|
787
|
-
if(attribute.value.length>2048){
|
|
788
|
-
element.setAttribute(attribute.name,attribute.value.slice(0,2048));
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
if(name==='CODE'&&element.hasAttribute('class')){
|
|
793
|
-
const className=element.getAttribute('class').trim();
|
|
794
|
-
if(!/^language-[a-z0-9][a-z0-9_+-]{0,63}$/i.test(className)){
|
|
795
|
-
element.removeAttribute('class');
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
for(const attribute of ['colspan','rowspan']){
|
|
800
|
-
if(element.hasAttribute(attribute)){
|
|
801
|
-
sanitizeIntegerAttribute(element,attribute,1,100);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
if(name==='OL'&&element.hasAttribute('start')){
|
|
805
|
-
sanitizeIntegerAttribute(element,'start',-100000,100000);
|
|
806
|
-
}
|
|
807
|
-
if(name==='LI'&&element.hasAttribute('value')){
|
|
808
|
-
sanitizeIntegerAttribute(element,'value',-100000,100000);
|
|
809
|
-
}
|
|
810
|
-
if(name==='TH'&&element.hasAttribute('scope')){
|
|
811
|
-
const scope=element.getAttribute('scope').toLowerCase();
|
|
812
|
-
if(!['col','colgroup','row','rowgroup'].includes(scope)){
|
|
813
|
-
element.removeAttribute('scope');
|
|
814
|
-
}else{
|
|
815
|
-
element.setAttribute('scope',scope);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
function sanitizeIntegerAttribute(element,name,min,max){
|
|
821
|
-
const value=Number(element.getAttribute(name));
|
|
822
|
-
if(!Number.isSafeInteger(value)||value<min||value>max){
|
|
823
|
-
element.removeAttribute(name);
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
element.setAttribute(name,String(value));
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
function sanitizeLink(link,sourceURL){
|
|
830
|
-
const originalHref=link.getAttribute('href')||'';
|
|
831
|
-
const policy=markdownDocumentLinkPolicy(
|
|
832
|
-
originalHref,
|
|
833
|
-
sourceURL,
|
|
834
|
-
pageOrigin
|
|
835
|
-
);
|
|
836
|
-
if(!policy){
|
|
837
|
-
link.removeAttribute('href');
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
link.setAttribute('href',policy.href);
|
|
842
|
-
link.dataset.markdownKind=policy.kind;
|
|
843
|
-
link.dataset.markdownOriginalHref=originalHref.slice(0,2048);
|
|
844
|
-
link.dataset.markdownTargetUrl=policy.targetURL;
|
|
845
|
-
link.dataset.markdownFragment=policy.fragment;
|
|
846
|
-
|
|
847
|
-
if(
|
|
848
|
-
policy.external
|
|
849
|
-
&&/^https?:$/i.test(new URL(policy.targetURL).protocol)
|
|
850
|
-
){
|
|
851
|
-
link.target='_blank';
|
|
852
|
-
link.rel='noopener noreferrer';
|
|
853
|
-
link.referrerPolicy='no-referrer';
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
function sanitizeImage(image,sourceURL){
|
|
858
|
-
const resolved=markdownDocumentAssetURL(
|
|
859
|
-
image.getAttribute('src')||'',
|
|
860
|
-
sourceURL,
|
|
861
|
-
pageOrigin
|
|
862
|
-
);
|
|
863
|
-
if(resolved){
|
|
864
|
-
image.setAttribute('src',resolved);
|
|
865
|
-
}else{
|
|
866
|
-
image.removeAttribute('src');
|
|
867
|
-
}
|
|
868
|
-
image.alt=image.getAttribute('alt')||'';
|
|
869
|
-
image.loading='lazy';
|
|
870
|
-
image.decoding='async';
|
|
871
|
-
image.referrerPolicy='no-referrer';
|
|
872
|
-
}
|
|
873
|
-
|
|
874
679
|
function decorateHeadings(fragment){
|
|
875
680
|
const usedHeadingIds=new Set();
|
|
876
681
|
return Array.from(fragment.querySelectorAll('h1,h2,h3,h4,h5,h6'))
|
|
@@ -884,7 +689,7 @@
|
|
|
884
689
|
const level=Number(heading.tagName.slice(1));
|
|
885
690
|
heading.id=id;
|
|
886
691
|
heading.tabIndex=-1;
|
|
887
|
-
return
|
|
692
|
+
return {id,level,text};
|
|
888
693
|
}
|
|
889
694
|
);
|
|
890
695
|
}
|
|
@@ -904,7 +709,7 @@
|
|
|
904
709
|
const label=headingText?`${prefix}: ${headingText}`:prefix;
|
|
905
710
|
|
|
906
711
|
table.tabIndex=0;
|
|
907
|
-
table.setAttribute('aria-label',label
|
|
712
|
+
table.setAttribute('aria-label',label);
|
|
908
713
|
}
|
|
909
714
|
);
|
|
910
715
|
}
|
|
@@ -963,7 +768,7 @@
|
|
|
963
768
|
:String(error||options.labels.error);
|
|
964
769
|
const code=String(error?.code||'ARCANE_MARKDOWN_DOCUMENT_RENDER_FAILED');
|
|
965
770
|
const reason=String(error?.reason||'markdown-document-render-failed');
|
|
966
|
-
transition('error',{message
|
|
771
|
+
transition('error',{message,code,reason},operationId);
|
|
967
772
|
return false;
|
|
968
773
|
}
|
|
969
774
|
|
|
@@ -974,13 +779,11 @@
|
|
|
974
779
|
state=nextState;
|
|
975
780
|
updateStateDOM();
|
|
976
781
|
|
|
977
|
-
const eventDetail=
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
);
|
|
782
|
+
const eventDetail={
|
|
783
|
+
...detail,
|
|
784
|
+
sourceURL:currentSourceURL,
|
|
785
|
+
state
|
|
786
|
+
};
|
|
984
787
|
const publicDetail={
|
|
985
788
|
characters:Number(detail.characters||currentMarkdown.length||0),
|
|
986
789
|
headingCount:Array.isArray(detail.headings)?detail.headings.length:tableOfContents.length,
|
|
@@ -1055,15 +858,13 @@
|
|
|
1055
858
|
return;
|
|
1056
859
|
}
|
|
1057
860
|
|
|
1058
|
-
const detail=
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1066
|
-
);
|
|
861
|
+
const detail={
|
|
862
|
+
fragment:link.dataset.markdownFragment||'',
|
|
863
|
+
href:link.dataset.markdownOriginalHref||link.getAttribute('href'),
|
|
864
|
+
kind:link.dataset.markdownKind||'link',
|
|
865
|
+
sourceURL:currentSourceURL,
|
|
866
|
+
targetURL:link.dataset.markdownTargetUrl||link.href
|
|
867
|
+
};
|
|
1067
868
|
operationSequence+=1;
|
|
1068
869
|
const operationId=`${events.descriptor.instanceId}:navigate:${operationSequence}`;
|
|
1069
870
|
const publication=events.dispatch('markdown-document-navigate',detail,{
|
|
@@ -2,16 +2,12 @@
|
|
|
2
2
|
<style>
|
|
3
3
|
:host{
|
|
4
4
|
display:block;
|
|
5
|
-
height:100%;
|
|
6
|
-
min-height:0;
|
|
7
5
|
}
|
|
8
6
|
|
|
9
7
|
.markdown-editor{
|
|
10
8
|
display:grid;
|
|
11
9
|
gap:.75em;
|
|
12
|
-
grid-template-rows:auto
|
|
13
|
-
height:100%;
|
|
14
|
-
min-height:0;
|
|
10
|
+
grid-template-rows:auto auto auto auto auto;
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
.entry-title,
|
|
@@ -25,7 +21,7 @@
|
|
|
25
21
|
background:var(--background);
|
|
26
22
|
border:1px solid var(--border-color);
|
|
27
23
|
border-radius:.5em;
|
|
28
|
-
overflow:
|
|
24
|
+
overflow:visible;
|
|
29
25
|
padding:1em;
|
|
30
26
|
}
|
|
31
27
|
|
|
@@ -235,7 +231,7 @@
|
|
|
235
231
|
markdown:textarea.value,
|
|
236
232
|
title:titleInput.value
|
|
237
233
|
};
|
|
238
|
-
emit('markdown-editor-change',detail,
|
|
234
|
+
emit('markdown-editor-change',detail,detail);
|
|
239
235
|
if(options.onChange){
|
|
240
236
|
Promise.resolve(options.onChange(detail)).catch(
|
|
241
237
|
error=>console.error('Markdown change callback failed:',error)
|
|
@@ -265,7 +261,7 @@
|
|
|
265
261
|
preview.querySelectorAll('a').forEach(
|
|
266
262
|
link=>{
|
|
267
263
|
link.target='_blank';
|
|
268
|
-
link.rel
|
|
264
|
+
link.removeAttribute('rel');
|
|
269
265
|
}
|
|
270
266
|
);
|
|
271
267
|
}
|
|
@@ -299,7 +295,7 @@
|
|
|
299
295
|
return false;
|
|
300
296
|
}
|
|
301
297
|
const markdown=textarea.value;
|
|
302
|
-
const entryTitle=titleInput.value
|
|
298
|
+
const entryTitle=titleInput.value;
|
|
303
299
|
if(!markdown.trim()){
|
|
304
300
|
status.innerText=options.labels.emptyError;
|
|
305
301
|
textarea.focus();
|
|
@@ -330,7 +326,8 @@
|
|
|
330
326
|
resetEditor(false);
|
|
331
327
|
}
|
|
332
328
|
status.innerText=options.labels.saved;
|
|
333
|
-
|
|
329
|
+
const detail={title:entryTitle,markdown};
|
|
330
|
+
emit('markdown-editor-saved',detail,detail,operationId);
|
|
334
331
|
return true;
|
|
335
332
|
}catch(error){
|
|
336
333
|
if(destroyed||generation!==saveGeneration||controller.signal.aborted){
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
|
-
<style>:host{display:block}.player-shell{display:grid;gap:var(--arcane-space-4)}.loader{display:flex;gap:var(--arcane-space-2)}.loader input{min-width:0;width:100%}.player{aspect-ratio:16/9;display:grid;place-items:center;
|
|
3
|
-
<section class="player-shell"><form id="loader" class="loader"><label class="visually-hidden" for="mediaUrl">Video or playlist address</label><input id="mediaUrl" type="text" inputmode="url" autocomplete="off" spellcheck="false" placeholder="Paste a YouTube video or playlist URL"><button class="arcane-button" type="submit">Play</button></form><div class="player"><div id="empty" class="empty"><strong>Choose something to play</strong><p>Paste a YouTube or YouTube Music video or playlist address.</p></div><iframe id="frame" title="Embedded media player" hidden allow="autoplay; encrypted-media; picture-in-picture; fullscreen" allowfullscreen
|
|
4
|
-
<script type="module">const host=this,root=host.shadowRoot,form=root.querySelector('#loader'),input=root.querySelector('#mediaUrl'),frame=root.querySelector('#frame'),empty=root.querySelector('#empty'),status=root.querySelector('#status');const componentURL=new URL(host.getAttribute('href')||'./arcane/components/media-embed.html',document.baseURI);const {parseYouTubeMedia,youtubeEmbedUrl}=await import(new URL('../modules/YouTubeMedia.js',componentURL));const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.media-embed',eventTypes:['media-open-platform','media-load','media-error','media-embed-ready']}),lifecycleController=new AbortController();let platformUrl='https://www.youtube.com/',privacyEnhanced=
|
|
2
|
+
<style>:host{display:block}.player-shell{display:grid;gap:var(--arcane-space-4)}.loader{display:flex;gap:var(--arcane-space-2)}.loader input{min-width:0;width:100%}.player{aspect-ratio:16/9;display:grid;place-items:center;background:var(--arcane-surface-muted);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-large)}iframe{width:100%;height:100%;border:0}.empty{text-align:center;color:var(--arcane-muted);padding:var(--arcane-space-5)}.footer{align-items:center;display:flex;gap:var(--arcane-space-3);justify-content:space-between}.status{margin:0;color:var(--arcane-muted)}@media(max-width:36rem){.loader,.footer{align-items:stretch;flex-direction:column}}</style>
|
|
3
|
+
<section class="player-shell"><form id="loader" class="loader"><label class="visually-hidden" for="mediaUrl">Video or playlist address</label><input id="mediaUrl" type="text" inputmode="url" autocomplete="off" spellcheck="false" placeholder="Paste a YouTube video or playlist URL"><button class="arcane-button" type="submit">Play</button></form><div class="player"><div id="empty" class="empty"><strong>Choose something to play</strong><p>Paste a YouTube or YouTube Music video or playlist address.</p></div><iframe id="frame" title="Embedded media player" hidden allow="autoplay; encrypted-media; picture-in-picture; fullscreen" allowfullscreen></iframe></div><div class="footer"><p id="status" class="status" role="status" aria-live="polite">Nothing loaded.</p><button id="platform" class="arcane-button arcane-button--secondary" type="button">Open platform</button></div></section>
|
|
4
|
+
<script type="module">const host=this,root=host.shadowRoot,form=root.querySelector('#loader'),input=root.querySelector('#mediaUrl'),frame=root.querySelector('#frame'),empty=root.querySelector('#empty'),status=root.querySelector('#status');const componentURL=new URL(host.getAttribute('href')||'./arcane/components/media-embed.html',document.baseURI);const {parseYouTubeMedia,youtubeEmbedUrl}=await import(new URL('../modules/YouTubeMedia.js',componentURL));const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.media-embed',eventTypes:['media-open-platform','media-load','media-error','media-embed-ready']}),lifecycleController=new AbortController();let platformUrl='https://www.youtube.com/',privacyEnhanced=false,destroyed=false,operationSequence=0;
|
|
5
5
|
function nextOperationId(kind){operationSequence+=1;return `${events.descriptor.instanceId}:${kind}:${operationSequence}`}
|
|
6
6
|
function emit(type,detail={},publicDetail={},operationId=null){if(destroyed)return false;const publication=events.dispatch(type,detail,{publicDetail,operationId});return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true})}
|
|
7
|
-
host.configure=options=>{if(destroyed)return false;platformUrl=String(options?.platformUrl||platformUrl);privacyEnhanced=options
|
|
8
|
-
form.addEventListener('submit',event=>{event.preventDefault();load(input.value)},{signal:lifecycleController.signal});root.querySelector('#platform').addEventListener('click',()=>
|
|
9
|
-
function load(value){if(destroyed)return null;const operationId=nextOperationId('load');try{const locator=parseYouTubeMedia(value),url=youtubeEmbedUrl(locator,{privacyEnhanced});frame.src=url;frame.hidden=false;empty.hidden=true;status.textContent=locator.type==='playlist'?'Playlist loaded.':'Video loaded.';emit('media-load',
|
|
7
|
+
host.configure=options=>{if(destroyed)return false;platformUrl=String(options?.platformUrl||platformUrl);if(Object.hasOwn(options||{},'privacyEnhanced'))privacyEnhanced=options.privacyEnhanced===true;if(options?.placeholder)input.placeholder=String(options.placeholder);if(options?.openLabel)root.querySelector('#platform').textContent=String(options.openLabel);if(options?.initialValue){input.value=String(options.initialValue);load(input.value)}return host};host.load=load;host.destroy=destroy;
|
|
8
|
+
form.addEventListener('submit',event=>{event.preventDefault();load(input.value)},{signal:lifecycleController.signal});root.querySelector('#platform').addEventListener('click',()=>{const detail={url:platformUrl,destination:'platform'};emit('media-open-platform',detail,detail,nextOperationId('open-platform'))},{signal:lifecycleController.signal});
|
|
9
|
+
function load(value){if(destroyed)return null;const operationId=nextOperationId('load');try{const locator=parseYouTubeMedia(value),url=youtubeEmbedUrl(locator,{privacyEnhanced}),detail={locator,url};frame.src=url;frame.hidden=false;empty.hidden=true;status.textContent=locator.type==='playlist'?'Playlist loaded.':'Video loaded.';emit('media-load',detail,detail,operationId);return locator}catch(error){const code=String(error?.code||'ARCANE_MEDIA_EMBED_SOURCE_INVALID'),reason='media-source-invalid',detail={error,code,reason};status.textContent=error.message;frame.hidden=true;empty.hidden=false;emit('media-error',detail,detail,operationId);return null}}
|
|
10
10
|
function destroy(){if(destroyed)return false;destroyed=true;lifecycleController.abort('media-embed-destroyed');frame.src='about:blank';frame.removeAttribute('src');frame.hidden=true;events.dispose();host.ready=false;return true}
|
|
11
11
|
host.ready=true;emit('media-embed-ready',{}, {ready:true});</script>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
|
-
<style>:host{display:block}.capture{display:grid;gap:var(--arcane-space-4)}.actions{display:grid;gap:var(--arcane-space-3);grid-template-columns:repeat(3,minmax(0,1fr))}.action{display:grid;gap:var(--arcane-space-2);padding:var(--arcane-space-4);text-align:left}.action strong{font-size:1.05rem}.action span{color:var(--arcane-muted);font-size:.85rem}.recording{align-items:center;display:flex;gap:var(--arcane-space-3);justify-content:space-between;padding:var(--arcane-space-3);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-medium)}.preview{display:grid;place-items:center;min-height:16rem;overflow:
|
|
3
|
-
<section class="capture"><div class="actions"><button class="action arcane-card" type="button" data-capture="image"><strong>Screenshot</strong><span>Capture the selected display as PNG.</span></button><button class="action arcane-card" type="button" data-capture="video"><strong>Record video</strong><span>Record the selected display in a browser-supported video format.</span></button><button class="action arcane-card" type="button" data-capture="gif"><strong>Record GIF</strong><span>Record
|
|
2
|
+
<style>:host{display:block}.capture{display:grid;gap:var(--arcane-space-4)}.actions{display:grid;gap:var(--arcane-space-3);grid-template-columns:repeat(3,minmax(0,1fr))}.action{display:grid;gap:var(--arcane-space-2);padding:var(--arcane-space-4);text-align:left}.action strong{font-size:1.05rem}.action span{color:var(--arcane-muted);font-size:.85rem}.recording{align-items:center;display:flex;gap:var(--arcane-space-3);justify-content:space-between;padding:var(--arcane-space-3);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-medium)}.preview{display:grid;place-items:center;min-height:16rem;overflow:auto;background:var(--arcane-surface-muted);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-large)}.preview :where(img,video){display:block}.empty{color:var(--arcane-muted);text-align:center}.status{margin:0;color:var(--arcane-muted)}@media(max-width:44rem){.actions{grid-template-columns:1fr}}</style>
|
|
3
|
+
<section class="capture"><div class="actions"><button class="action arcane-card" type="button" data-capture="image"><strong>Screenshot</strong><span>Capture the selected display as PNG.</span></button><button class="action arcane-card" type="button" data-capture="video"><strong>Record video</strong><span>Record the selected display in a browser-supported video format.</span></button><button class="action arcane-card" type="button" data-capture="gif"><strong>Record GIF</strong><span>Record the selected display as an animated GIF.</span></button></div><div id="recording" class="recording" hidden><span id="recordingLabel">Recording…</span><button id="stop" class="arcane-button" type="button">Stop recording</button></div><div id="preview" class="preview"><div class="empty"><strong>No capture yet</strong><p>Your capture remains on this device unless you choose to save it.</p></div></div><p id="status" class="status" role="status" aria-live="polite">Choose a capture type. Your browser will ask which display to share.</p></section>
|
|
4
4
|
<script type="module">const host=this,root=host.shadowRoot,preview=root.querySelector('#preview'),recording=root.querySelector('#recording'),status=root.querySelector('#status');const componentURL=new URL(host.getAttribute('href')||'./arcane/components/screen-capture.html',document.baseURI);const {default:ScreenCapture}=await import(new URL('../modules/ScreenCapture.js',componentURL));const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.screen-capture',eventTypes:['screen-capture-result','screen-capture-ready']});const capture=new ScreenCapture(),listeners=new AbortController();let destroyed=false,operationGeneration=0,previewUrl='';
|
|
5
5
|
function emit(type,detail={},publicDetail={},operationId=null){if(destroyed)return false;const publication=events.dispatch(type,detail,{operationId,publicDetail});return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true})}
|
|
6
6
|
function current(generation){return !destroyed&&generation===operationGeneration}
|
|
7
|
-
function cleanupCaptureStart(originalError=null){const cleanupErrors=[];
|
|
7
|
+
function cleanupCaptureStart(originalError=null){const cleanupErrors=[];if(capture.recorder&&capture.recorder.state!=='inactive'){try{capture.recorder.stop()}catch(error){cleanupErrors.push(error)}}try{capture.stopTracks(capture.stream)}catch(error){cleanupErrors.push(error)}try{capture.reset()}catch(error){cleanupErrors.push(error)}for(const error of cleanupErrors)console.error('Error cleaning up rejected screen capture start:',error);return originalError}
|
|
8
8
|
async function onCapture(event){const mode=event.target.closest('[data-capture]')?.dataset.capture;if(!mode||destroyed)return;const generation=++operationGeneration;setButtons(true);try{if(mode==='image'){status.textContent='Choose a display to capture…';const result=await capture.captureImage();if(!current(generation))return;showResult(result,generation)}else{status.textContent='Choose a display to record…';if(mode==='video')await capture.startVideo();else await capture.startGif();if(!current(generation)){cleanupCaptureStart();return}recording.hidden=false;root.querySelector('#recordingLabel').textContent=mode==='gif'?'Recording GIF frames…':'Recording video…';status.textContent='Recording. Return here and press Stop when finished.'}}catch(error){const originalError=mode==='image'?error:cleanupCaptureStart(error);if(!current(generation))return;status.textContent=originalError.message;setButtons(false)}}
|
|
9
9
|
async function onStop(){if(destroyed)return;const generation=operationGeneration;root.querySelector('#stop').disabled=true;status.textContent='Finishing capture…';try{const result=await capture.stop();if(current(generation)&&result)showResult(result,generation)}catch(error){if(current(generation))status.textContent=error.message}finally{if(current(generation)){recording.hidden=true;root.querySelector('#stop').disabled=false;setButtons(false)}}}
|
|
10
10
|
root.querySelector('.actions').addEventListener('click',onCapture,{signal:listeners.signal});root.querySelector('#stop').addEventListener('click',onStop,{signal:listeners.signal});
|
|
11
11
|
function setButtons(value){for(const button of root.querySelectorAll('[data-capture]'))button.disabled=value}
|
|
12
|
-
function showResult(result,generation){if(!current(generation))return false;if(previewUrl)URL.revokeObjectURL(previewUrl);previewUrl=URL.createObjectURL(result.blob);const media=document.createElement(result.mimeType.startsWith('video/')?'video':'img');media.src=previewUrl;if(media.tagName==='VIDEO')media.controls=true;media.alt='Captured display preview';preview.replaceChildren(media);status.textContent
|
|
12
|
+
function showResult(result,generation){if(!current(generation))return false;if(previewUrl)URL.revokeObjectURL(previewUrl);previewUrl=URL.createObjectURL(result.blob);const media=document.createElement(result.mimeType.startsWith('video/')?'video':'img');media.src=previewUrl;if(media.tagName==='VIDEO')media.controls=true;media.alt='Captured display preview';preview.replaceChildren(media);status.textContent='Capture ready.';setButtons(false);return emit('screen-capture-result',{result},{result},`screen-capture-${events.instanceId}-${generation}`)}
|
|
13
13
|
function destroy(){if(destroyed)return false;destroyed=true;++operationGeneration;listeners.abort('screen-capture-component-destroyed');host.ready=false;recording.hidden=true;setButtons(true);root.querySelector('#stop').disabled=true;if(previewUrl){URL.revokeObjectURL(previewUrl);previewUrl=''}let error=null;try{capture.destroy()}catch(value){error=value}finally{events.dispose()}if(error)throw error;return true}
|
|
14
14
|
host.capture=capture;host.destroy=destroy;host.ready=true;emit('screen-capture-ready',{}, {ready:true},`screen-capture-ready-${events.instanceId}`);</script>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<label>Alleged actor<select data-actor></select></label>
|
|
37
37
|
<label>Disposition<select data-disposition></select></label>
|
|
38
38
|
<label>Confidence<select data-confidence></select></label>
|
|
39
|
-
<label class="notes">Notes, contrary facts, and follow-up<textarea data-notes
|
|
39
|
+
<label class="notes">Notes, contrary facts, and follow-up<textarea data-notes></textarea></label>
|
|
40
40
|
<div class="save-row"><button type="submit">Save decision</button><span class="save-state" data-save-state aria-live="polite"></span></div>
|
|
41
41
|
</form>
|
|
42
42
|
</section>
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
|
|
83
83
|
function showFinding(value={},config={}){
|
|
84
84
|
finding=value&&typeof value==='object'?value:null; if(!finding) throw new TypeError('A finding is required.');
|
|
85
|
-
sources=
|
|
85
|
+
sources=Array.isArray(finding.sources)?finding.sources:[];
|
|
86
86
|
title.textContent=String(finding.label||'Investigative lead'); assessment.textContent=String(finding.assessment||finding.conclusion||'Human review is required before reliance.');
|
|
87
87
|
badges.replaceChildren();
|
|
88
88
|
for(const value of [finding.allegedActorLabel?`Alleged actor: ${finding.allegedActorLabel}`:'Actor not assigned',finding.kind||'Investigative lead',finding.confidence?`${finding.confidence} confidence`:'Confidence not assigned']){const badge=document.createElement('span'); badge.className='badge'; badge.textContent=value; badges.append(badge);}
|