arcane-os 0.2.2 → 0.3.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/CHANGELOG.md +29 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
- package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
- package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
- package/browser-runtime/ai/browser-speech.mjs +2 -0
- package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
- package/browser-runtime/ai/model-controller.mjs +285 -95
- package/browser-runtime/ai/speech-worker-client.mjs +247 -32
- package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
- package/browser-runtime/event-manager.mjs +1097 -1
- package/docs/architecture.md +2 -2
- package/docs/event-manager.md +155 -27
- package/docs/reference/README.md +27 -27
- package/docs/reference/ai/browser-speech-package-authority.json +835 -0
- package/docs/reference/ai/browser-speech.md +1162 -246
- package/docs/reference/ai/browser-wasm.md +18 -7
- package/docs/reference/availability-and-normalization.md +6 -3
- package/docs/reference/behavioral-testing.md +29 -6
- package/docs/reference/cli.md +117 -9
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/event-manager.md +577 -32
- package/docs/reference/inventory/package-api.json +478 -2
- package/docs/reference/inventory/runtime-components.json +108 -44
- package/docs/reference/inventory/runtime-modules.json +131 -53
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +157 -43
- package/docs/reference/runtime-components.md +258 -83
- package/docs/reference/runtime-modules.md +613 -77
- package/docs/reference/sdk-api.md +1014 -25
- package/package.json +5 -4
- package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
- package/runtime/arcane/components/app-bar.html +34 -13
- package/runtime/arcane/components/assistant-panel.html +110 -57
- package/runtime/arcane/components/calculator.html +7 -4
- package/runtime/arcane/components/chart.html +58 -17
- package/runtime/arcane/components/chat.html +606 -136
- package/runtime/arcane/components/conversation-view.html +13 -6
- package/runtime/arcane/components/dashboard-config.html +96 -59
- package/runtime/arcane/components/data-maintenance.html +69 -14
- package/runtime/arcane/components/data-view.html +53 -7
- package/runtime/arcane/components/directory-picker.html +118 -32
- package/runtime/arcane/components/document-inspector.html +47 -10
- package/runtime/arcane/components/file-drop.html +81 -35
- package/runtime/arcane/components/file-inspector.html +72 -22
- package/runtime/arcane/components/file-manager.html +374 -79
- package/runtime/arcane/components/integration-settings.html +12 -5
- package/runtime/arcane/components/local-ai-status.html +48 -19
- package/runtime/arcane/components/markdown-document.html +161 -68
- package/runtime/arcane/components/markdown-editor.html +110 -33
- package/runtime/arcane/components/media-embed.html +8 -5
- package/runtime/arcane/components/modal.html +15 -5
- package/runtime/arcane/components/output-panel.html +28 -23
- package/runtime/arcane/components/preferences-form.html +22 -4
- package/runtime/arcane/components/record-timeline.html +18 -2
- package/runtime/arcane/components/relationship-board.html +23 -3
- package/runtime/arcane/components/screen-capture.html +10 -4
- package/runtime/arcane/components/source-code-viewer.html +76 -9
- package/runtime/arcane/components/source-explanation.html +23 -3
- package/runtime/arcane/components/speech.html +462 -384
- package/runtime/arcane/components/summary-strip.html +22 -11
- package/runtime/arcane/components/table.html +39 -21
- package/runtime/arcane/components/task-progress.html +79 -21
- package/runtime/arcane/components/terminal-workspace.html +7 -4
- package/runtime/arcane/components/theme-editor.html +7 -3
- package/runtime/arcane/components/unified-inbox.html +9 -4
- package/runtime/arcane/components/voice-transcription.html +639 -98
- package/runtime/arcane/components/weather-widget.html +5 -3
- package/runtime/arcane/components/web-navigator.html +48 -8
- package/runtime/arcane/entities/Chat.js +1 -1
- package/runtime/arcane/entities/User.js +110 -23
- package/runtime/arcane/modules/AI.js +2109 -130
- package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
- package/runtime/arcane/modules/AIRuntimeState.js +109 -52
- package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
- package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
- package/runtime/arcane/modules/CalculatorEngine.js +63 -3
- package/runtime/arcane/modules/CommunicationAppController.js +588 -28
- package/runtime/arcane/modules/CommunicationHub.js +590 -10
- package/runtime/arcane/modules/ComponentContracts.js +470 -0
- package/runtime/arcane/modules/ConversationTimebox.js +152 -33
- package/runtime/arcane/modules/DBLS.js +40 -7
- package/runtime/arcane/modules/DBOPFS.js +35 -11
- package/runtime/arcane/modules/DataMaintenance.js +12 -2
- package/runtime/arcane/modules/Errors.js +65 -7
- package/runtime/arcane/modules/HTMLImport.js +198 -14
- package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
- package/runtime/arcane/modules/Mail.js +738 -115
- package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
- package/runtime/arcane/modules/MailTransport.mjs +197 -39
- package/runtime/arcane/modules/Ollama.js +36 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
- package/runtime/arcane/modules/PreferenceStore.js +367 -33
- package/runtime/arcane/modules/RecordReviewStore.js +322 -23
- package/runtime/arcane/modules/ScreenCapture.js +1397 -15
- package/runtime/arcane/modules/SpeechPlayback.js +438 -41
- package/runtime/arcane/modules/TerminalClient.js +277 -12
- package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
- package/runtime/arcane/modules/ThemeManager.js +39 -7
- package/runtime/arcane/modules/TimeGuard.js +131 -20
- package/runtime/arcane/modules/WaitForComponent.js +386 -33
- package/schemas/arcane-lock.schema.json +2 -2
- package/src/cli/main.mjs +435 -9
- package/src/event-manager.mjs +1097 -1
- package/src/import-map.mjs +21 -3
- package/src/index.mjs +13 -0
- package/src/installed-sdk-runtime.mjs +112 -0
- package/src/mail-api.mjs +22 -0
- package/src/mail-credentials.mjs +667 -0
- package/src/mail-server.mjs +1769 -0
- package/src/mail.mjs +261 -0
- package/src/sdk-browser-runtime.mjs +85 -41
- package/src/testing-loader.mjs +7 -0
- package/src/toolchain.mjs +3 -0
- package/src/workspace.mjs +1 -1
|
@@ -4,11 +4,18 @@
|
|
|
4
4
|
</style>
|
|
5
5
|
<section class="arcane-card"><header class="arcane-card__header heading"><div><h2 id="title" class="arcane-section-heading">Connected services</h2><p id="description" class="arcane-help">Choose the services that appear in this application.</p></div><button id="close" class="arcane-button arcane-button--tertiary" type="button">Close</button></header><div class="arcane-card__body"><p class="notice">Passwords and API secrets are never stored here. Arcane delegates credentials to the local communications bridge or the provider's authorization flow.</p><div id="services" class="services"></div></div><footer class="arcane-card__footer"><p id="message" class="arcane-help" role="status" aria-live="polite"></p><button id="save" class="arcane-button" type="button">Save services</button></footer></section>
|
|
6
6
|
<script type="module">
|
|
7
|
-
const host=this,root=host.shadowRoot,services=root.querySelector('#services'),message=root.querySelector('#message');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const host=this,root=host.shadowRoot,services=root.querySelector('#services'),message=root.querySelector('#message');
|
|
8
|
+
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
9
|
+
const events=createArcaneEventSource(host,{source:'arcane.component.integration-settings',eventTypes:['integration-settings-close','integration-settings-save','integration-action','integration-settings-ready']});
|
|
10
|
+
const lifecycleController=new AbortController();
|
|
11
|
+
let descriptors=[],values={},destroyed=false,operationSequence=0;
|
|
12
|
+
function nextOperationId(kind){operationSequence+=1;return `${events.descriptor.instanceId}:${kind}:${operationSequence}`}
|
|
13
|
+
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})}
|
|
14
|
+
host.configure=options=>{if(destroyed)return false;descriptors=Array.from(options?.services||[]);values={...(options?.values||{})};root.querySelector('#title').textContent=options?.title||'Connected services';root.querySelector('#description').textContent=options?.description||'Choose the services that appear in this application.';render();return host};host.getValues=()=>destroyed?{}:read();host.setStatus=value=>{if(destroyed)return false;message.textContent=String(value||'')};
|
|
15
|
+
root.querySelector('#close').addEventListener('click',()=>emit('integration-settings-close',{}, {},nextOperationId('close')),{signal:lifecycleController.signal});root.querySelector('#save').addEventListener('click',()=>emit('integration-settings-save',{values:read()},{serviceCount:descriptors.length},nextOperationId('save')),{signal:lifecycleController.signal});
|
|
16
|
+
function render(){services.replaceChildren(...descriptors.map(item=>{const section=document.createElement('section');section.className='service';section.dataset.id=item.id;const head=document.createElement('div');head.className='service-head';const enabled=document.createElement('input');enabled.type='checkbox';enabled.name='enabled';enabled.checked=Boolean(values[item.id]?.enabled);enabled.setAttribute('aria-label',`Enable ${item.label}`);const copy=document.createElement('div');copy.className='service-copy';const label=document.createElement('strong');label.textContent=item.label;const description=document.createElement('p');description.textContent=item.description||'';const state=document.createElement('span');state.className='status';state.textContent=values[item.id]?.status||item.defaultStatus||'Disconnected';copy.append(label,description);head.append(enabled,copy,state);section.append(head);const fields=document.createElement('div');fields.className='fields';if(item.acceptsEndpoint){fields.append(field('Bridge URL','endpoint',values[item.id]?.endpoint||item.defaultEndpoint||'http://127.0.0.1:8020'),item.id)}if(item.acceptsAccountLabel!==false)fields.append(field('Account label','accountLabel',values[item.id]?.accountLabel||'',item.id));if(fields.childElementCount)section.append(fields);if(item.actionLabel){const actions=document.createElement('div');actions.className='actions';const action=document.createElement('button');action.type='button';action.className='arcane-button arcane-button--secondary';action.textContent=item.actionLabel;action.addEventListener('click',()=>emit('integration-action',{service:item,values:read()[item.id]},{serviceId:String(item.id)},nextOperationId('service-action')),{signal:lifecycleController.signal});actions.append(action);section.append(actions)}return section}))}
|
|
11
17
|
function field(labelText,name,value,id){const label=document.createElement('label');label.className='arcane-field';const span=document.createElement('span');span.textContent=labelText;const input=document.createElement('input');input.name=name;input.value=value;input.dataset.service=id;input.autocomplete='off';label.append(span,input);return label}
|
|
12
18
|
function read(){return Object.fromEntries(descriptors.map(item=>{const section=services.querySelector(`[data-id="${CSS.escape(item.id)}"]`);return [item.id,{enabled:section.querySelector('[name="enabled"]').checked,endpoint:section.querySelector('[name="endpoint"]')?.value.trim()||'',accountLabel:section.querySelector('[name="accountLabel"]')?.value.trim()||'',status:values[item.id]?.status||item.defaultStatus||'Disconnected'}]}))}
|
|
13
|
-
|
|
19
|
+
function destroy(){if(destroyed)return false;destroyed=true;lifecycleController.abort();descriptors=[];values={};events.dispose();host.ready=false;return true}
|
|
20
|
+
host.destroy=destroy;host.ready=true;emit('integration-settings-ready',{}, {ready:true});
|
|
14
21
|
</script>
|
|
@@ -97,6 +97,9 @@
|
|
|
97
97
|
<script type="module">
|
|
98
98
|
const host=this;
|
|
99
99
|
const root=host.shadowRoot;
|
|
100
|
+
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
101
|
+
const events=createArcaneEventSource(host,{source:'arcane.component.local-ai-status',eventTypes:['local-ai-retry','local-ai-status-dismissed','local-ai-status-ready']});
|
|
102
|
+
const lifecycleController=new AbortController();
|
|
100
103
|
const panel=root.querySelector('#panel');
|
|
101
104
|
const title=root.querySelector('#localAIStatusTitle');
|
|
102
105
|
const summary=root.querySelector('#summary');
|
|
@@ -118,28 +121,36 @@
|
|
|
118
121
|
let options={};
|
|
119
122
|
let readyDismissTimer=null;
|
|
120
123
|
let readyForDismiss=false;
|
|
124
|
+
let operationSequence=0;
|
|
125
|
+
let destroyed=false;
|
|
126
|
+
|
|
127
|
+
function emit(type,detail={},publicDetail={},operationId=null){
|
|
128
|
+
if(destroyed){
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const publication=events.dispatch(type,detail,{publicDetail,operationId});
|
|
132
|
+
return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true});
|
|
133
|
+
}
|
|
121
134
|
|
|
122
135
|
host.begin=begin;
|
|
123
136
|
host.configure=configure;
|
|
137
|
+
host.destroy=destroy;
|
|
124
138
|
host.present=present;
|
|
125
139
|
|
|
126
140
|
retry.addEventListener('click',function dispatchRetryRequest(){
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
composed:true
|
|
130
|
-
}));
|
|
131
|
-
});
|
|
141
|
+
emit('local-ai-retry',{}, {},`${events.descriptor.instanceId}:retry:${++operationSequence}`);
|
|
142
|
+
},{signal:lifecycleController.signal});
|
|
132
143
|
settings.addEventListener('click',function showNativeRecoveryHelp(){
|
|
133
144
|
nativeHelp.hidden=false;
|
|
134
145
|
nativeHelp.focus?.();
|
|
135
|
-
});
|
|
146
|
+
},{signal:lifecycleController.signal});
|
|
136
147
|
dismiss.addEventListener('click',function dismissStatusManually(){
|
|
137
148
|
hideStatus('manual');
|
|
138
|
-
});
|
|
139
|
-
host.addEventListener('pointerenter',cancelReadyDismiss);
|
|
140
|
-
host.addEventListener('pointerleave',scheduleReadyDismiss);
|
|
141
|
-
host.addEventListener('focusin',cancelReadyDismiss);
|
|
142
|
-
host.addEventListener('focusout',scheduleReadyDismiss);
|
|
149
|
+
},{signal:lifecycleController.signal});
|
|
150
|
+
host.addEventListener('pointerenter',cancelReadyDismiss,{signal:lifecycleController.signal});
|
|
151
|
+
host.addEventListener('pointerleave',scheduleReadyDismiss,{signal:lifecycleController.signal});
|
|
152
|
+
host.addEventListener('focusin',cancelReadyDismiss,{signal:lifecycleController.signal});
|
|
153
|
+
host.addEventListener('focusout',scheduleReadyDismiss,{signal:lifecycleController.signal});
|
|
143
154
|
|
|
144
155
|
function cancelReadyDismiss(){
|
|
145
156
|
if(readyDismissTimer!==null){
|
|
@@ -150,7 +161,7 @@
|
|
|
150
161
|
|
|
151
162
|
function scheduleReadyDismiss(){
|
|
152
163
|
cancelReadyDismiss();
|
|
153
|
-
if(
|
|
164
|
+
if(destroyed||!readyForDismiss){
|
|
154
165
|
return;
|
|
155
166
|
}
|
|
156
167
|
readyDismissTimer=setTimeout(autoDismissReadyStatus,readyDismissDelay);
|
|
@@ -158,7 +169,7 @@
|
|
|
158
169
|
|
|
159
170
|
function autoDismissReadyStatus(){
|
|
160
171
|
readyDismissTimer=null;
|
|
161
|
-
if(
|
|
172
|
+
if(destroyed||!readyForDismiss){
|
|
162
173
|
return;
|
|
163
174
|
}
|
|
164
175
|
if(host.matches(':hover')||root.activeElement){
|
|
@@ -171,14 +182,13 @@
|
|
|
171
182
|
cancelReadyDismiss();
|
|
172
183
|
readyForDismiss=false;
|
|
173
184
|
host.hidden=true;
|
|
174
|
-
|
|
175
|
-
bubbles:true,
|
|
176
|
-
composed:true,
|
|
177
|
-
detail:{reason}
|
|
178
|
-
}));
|
|
185
|
+
emit('local-ai-status-dismissed',{reason},{reason:String(reason)},`${events.descriptor.instanceId}:dismiss:${++operationSequence}`);
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
function configure(input={}){
|
|
189
|
+
if(destroyed){
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
182
192
|
options={
|
|
183
193
|
profileHref:host.dataset.profileHref||null,
|
|
184
194
|
...options,
|
|
@@ -193,6 +203,9 @@
|
|
|
193
203
|
}
|
|
194
204
|
|
|
195
205
|
function begin(requirements={}){
|
|
206
|
+
if(destroyed){
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
196
209
|
readyForDismiss=false;
|
|
197
210
|
cancelReadyDismiss();
|
|
198
211
|
host.hidden=false;
|
|
@@ -207,6 +220,9 @@
|
|
|
207
220
|
}
|
|
208
221
|
|
|
209
222
|
function present(report={}){
|
|
223
|
+
if(destroyed){
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
210
226
|
const runtime=report.runtime||{};
|
|
211
227
|
const results=report.slots||report.results||report;
|
|
212
228
|
const requiredEntries=Object.keys(labels).map(function createRequiredEntry(key){
|
|
@@ -353,8 +369,21 @@
|
|
|
353
369
|
services.replaceChildren(fragment);
|
|
354
370
|
}
|
|
355
371
|
|
|
372
|
+
function destroy(){
|
|
373
|
+
if(destroyed){
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
destroyed=true;
|
|
377
|
+
cancelReadyDismiss();
|
|
378
|
+
readyForDismiss=false;
|
|
379
|
+
lifecycleController.abort('local-ai-status-destroyed');
|
|
380
|
+
events.dispose();
|
|
381
|
+
host.ready=false;
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
|
|
356
385
|
configure();
|
|
357
386
|
host.hidden=true;
|
|
358
387
|
host.ready=true;
|
|
359
|
-
|
|
388
|
+
emit('local-ai-status-ready',{}, {ready:true});
|
|
360
389
|
</script>
|
|
@@ -220,6 +220,9 @@
|
|
|
220
220
|
);
|
|
221
221
|
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
222
222
|
const {default:MD}=await import(moduleURL('MD.js?v=2'));
|
|
223
|
+
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
224
|
+
const events=createArcaneEventSource(host,{source:'arcane.component.markdown-document',eventTypes:['markdown-document-state','markdown-document-empty','markdown-document-error','markdown-document-loading','markdown-document-rendered','markdown-document-navigate','markdown-document-ready']});
|
|
225
|
+
const lifecycleController=new AbortController();
|
|
223
226
|
|
|
224
227
|
const documentElement=host.shadowRoot.querySelector('#document');
|
|
225
228
|
const statusElement=host.shadowRoot.querySelector('#status');
|
|
@@ -436,9 +439,13 @@
|
|
|
436
439
|
let options=normalizeOptions(optionsFromDataset(),defaultOptions);
|
|
437
440
|
let state='empty';
|
|
438
441
|
let tableOfContents=[];
|
|
442
|
+
let operationSequence=0;
|
|
443
|
+
let loadController=null;
|
|
444
|
+
let destroyed=false;
|
|
439
445
|
|
|
440
446
|
host.clear=clear;
|
|
441
447
|
host.configure=configure;
|
|
448
|
+
host.destroy=destroy;
|
|
442
449
|
host.fail=fail;
|
|
443
450
|
host.focus=focusDocument;
|
|
444
451
|
host.focusFragment=focusFragment;
|
|
@@ -448,15 +455,15 @@
|
|
|
448
455
|
Object.defineProperties(
|
|
449
456
|
host,
|
|
450
457
|
{
|
|
451
|
-
options:{get:getOptions,set:configure},
|
|
452
|
-
sourceURL:{get:()=>currentSourceURL,set:setSourceURL},
|
|
453
|
-
state:{get:()=>state},
|
|
454
|
-
tableOfContents:{get:()=>tableOfContents.map(entry=>({...entry}))},
|
|
455
|
-
value:{get:()=>currentMarkdown,set:value=>render(value)}
|
|
458
|
+
options:{configurable:true,get:getOptions,set:configure},
|
|
459
|
+
sourceURL:{configurable:true,get:()=>currentSourceURL,set:setSourceURL},
|
|
460
|
+
state:{configurable:true,get:()=>state},
|
|
461
|
+
tableOfContents:{configurable:true,get:()=>tableOfContents.map(entry=>({...entry}))},
|
|
462
|
+
value:{configurable:true,get:()=>currentMarkdown,set:value=>render(value)}
|
|
456
463
|
}
|
|
457
464
|
);
|
|
458
465
|
|
|
459
|
-
documentElement.addEventListener('click',handleNavigation);
|
|
466
|
+
documentElement.addEventListener('click',handleNavigation,{signal:lifecycleController.signal});
|
|
460
467
|
|
|
461
468
|
function optionsFromDataset(){
|
|
462
469
|
const configured={};
|
|
@@ -533,15 +540,31 @@
|
|
|
533
540
|
}
|
|
534
541
|
|
|
535
542
|
function configure(input={}){
|
|
543
|
+
if(destroyed){
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
536
546
|
const priorSourceURL=options.sourceURL;
|
|
537
|
-
|
|
547
|
+
const normalized=normalizeOptions(input,options);
|
|
548
|
+
const wasLoading=state==='loading';
|
|
549
|
+
supersedeLoad('markdown-configuration-replaced');
|
|
550
|
+
options=normalized;
|
|
538
551
|
renderLabels();
|
|
539
552
|
|
|
540
553
|
if(!currentMarkdown){
|
|
541
554
|
currentSourceURL=options.sourceURL;
|
|
542
|
-
|
|
555
|
+
if(wasLoading){
|
|
556
|
+
transition(
|
|
557
|
+
'empty',
|
|
558
|
+
{reason:'markdown-configuration-replaced'},
|
|
559
|
+
`${events.descriptor.instanceId}:configure:${++operationSequence}`
|
|
560
|
+
);
|
|
561
|
+
}else{
|
|
562
|
+
updateStateDOM();
|
|
563
|
+
}
|
|
543
564
|
}else if(priorSourceURL!==options.sourceURL){
|
|
544
565
|
render(currentMarkdown,{sourceURL:options.sourceURL});
|
|
566
|
+
}else if(wasLoading){
|
|
567
|
+
render(currentMarkdown,{sourceURL:options.sourceURL});
|
|
545
568
|
}else{
|
|
546
569
|
buildTableOfContents(tableOfContents);
|
|
547
570
|
updateStateDOM();
|
|
@@ -573,37 +596,61 @@
|
|
|
573
596
|
}
|
|
574
597
|
|
|
575
598
|
function setSourceURL(value=''){
|
|
576
|
-
|
|
599
|
+
if(destroyed){
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
const normalized=normalizeOptions({sourceURL:value},options);
|
|
603
|
+
const wasLoading=state==='loading';
|
|
604
|
+
supersedeLoad('markdown-source-replaced');
|
|
605
|
+
options=normalized;
|
|
577
606
|
if(currentMarkdown){
|
|
578
607
|
render(currentMarkdown,{sourceURL:options.sourceURL});
|
|
579
608
|
}else{
|
|
580
609
|
currentSourceURL=options.sourceURL;
|
|
581
|
-
|
|
610
|
+
if(wasLoading){
|
|
611
|
+
transition(
|
|
612
|
+
'empty',
|
|
613
|
+
{reason:'markdown-source-replaced'},
|
|
614
|
+
`${events.descriptor.instanceId}:source:${++operationSequence}`
|
|
615
|
+
);
|
|
616
|
+
}else{
|
|
617
|
+
updateStateDOM();
|
|
618
|
+
}
|
|
582
619
|
}
|
|
583
620
|
return currentSourceURL;
|
|
584
621
|
}
|
|
585
622
|
|
|
586
623
|
async function load(source='',loadOptions={}){
|
|
587
|
-
|
|
624
|
+
if(destroyed){
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
supersedeLoad('markdown-load-replaced');
|
|
628
|
+
const controller=new AbortController();
|
|
629
|
+
loadController=controller;
|
|
630
|
+
const sequence=loadSequence;
|
|
631
|
+
const operationId=`${events.descriptor.instanceId}:load:${++operationSequence}`;
|
|
588
632
|
let sourceURL;
|
|
589
633
|
try{
|
|
590
634
|
sourceURL=sourceURLFrom(loadOptions,currentSourceURL||options.sourceURL);
|
|
591
635
|
}catch(error){
|
|
592
636
|
if(sequence===loadSequence){
|
|
593
|
-
showError(error);
|
|
637
|
+
showError(error,operationId);
|
|
638
|
+
}
|
|
639
|
+
if(loadController===controller){
|
|
640
|
+
loadController=null;
|
|
594
641
|
}
|
|
595
642
|
return false;
|
|
596
643
|
}
|
|
597
644
|
|
|
598
645
|
currentSourceURL=sourceURL;
|
|
599
|
-
transition('loading');
|
|
646
|
+
transition('loading',{},operationId);
|
|
600
647
|
|
|
601
648
|
try{
|
|
602
649
|
const pending=typeof source==='function'
|
|
603
|
-
?source({sourceURL})
|
|
650
|
+
?source({sourceURL,signal:controller.signal,operationId})
|
|
604
651
|
:source;
|
|
605
652
|
const result=await pending;
|
|
606
|
-
if(sequence!==loadSequence){
|
|
653
|
+
if(destroyed||sequence!==loadSequence){
|
|
607
654
|
return false;
|
|
608
655
|
}
|
|
609
656
|
|
|
@@ -616,32 +663,40 @@
|
|
|
616
663
|
result,
|
|
617
664
|
sourceURL
|
|
618
665
|
);
|
|
619
|
-
return renderMarkdown(result.markdown??result.value??'',sourceURL);
|
|
666
|
+
return renderMarkdown(result.markdown??result.value??'',sourceURL,operationId);
|
|
620
667
|
}
|
|
621
|
-
return renderMarkdown(result??'',sourceURL);
|
|
668
|
+
return renderMarkdown(result??'',sourceURL,operationId);
|
|
622
669
|
}catch(error){
|
|
623
|
-
if(sequence===loadSequence){
|
|
624
|
-
showError(error);
|
|
670
|
+
if(!destroyed&&sequence===loadSequence&&!controller.signal.aborted){
|
|
671
|
+
showError(error,operationId);
|
|
625
672
|
}
|
|
626
673
|
return false;
|
|
674
|
+
}finally{
|
|
675
|
+
if(loadController===controller){
|
|
676
|
+
loadController=null;
|
|
677
|
+
}
|
|
627
678
|
}
|
|
628
679
|
}
|
|
629
680
|
|
|
630
681
|
function render(markdown='',renderOptions={}){
|
|
631
|
-
|
|
682
|
+
if(destroyed){
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
supersedeLoad('markdown-content-replaced');
|
|
686
|
+
const operationId=`${events.descriptor.instanceId}:render:${++operationSequence}`;
|
|
632
687
|
try{
|
|
633
688
|
const sourceURL=sourceURLFrom(
|
|
634
689
|
renderOptions,
|
|
635
690
|
currentSourceURL||options.sourceURL
|
|
636
691
|
);
|
|
637
|
-
return renderMarkdown(markdown,sourceURL);
|
|
692
|
+
return renderMarkdown(markdown,sourceURL,operationId);
|
|
638
693
|
}catch(error){
|
|
639
|
-
showError(error);
|
|
694
|
+
showError(error,operationId);
|
|
640
695
|
return false;
|
|
641
696
|
}
|
|
642
697
|
}
|
|
643
698
|
|
|
644
|
-
function renderMarkdown(markdown='',sourceURL=options.sourceURL){
|
|
699
|
+
function renderMarkdown(markdown='',sourceURL=options.sourceURL,operationId=null){
|
|
645
700
|
if(typeof markdown!=='string'){
|
|
646
701
|
throw new TypeError('Markdown document content must be a string.');
|
|
647
702
|
}
|
|
@@ -653,7 +708,7 @@
|
|
|
653
708
|
tableOfContentsList.replaceChildren();
|
|
654
709
|
|
|
655
710
|
if(!markdown.trim()){
|
|
656
|
-
transition('empty');
|
|
711
|
+
transition('empty',{},operationId);
|
|
657
712
|
return true;
|
|
658
713
|
}
|
|
659
714
|
|
|
@@ -667,7 +722,7 @@
|
|
|
667
722
|
||fragment.querySelector('img,hr,table')
|
|
668
723
|
);
|
|
669
724
|
if(!meaningful){
|
|
670
|
-
transition('empty');
|
|
725
|
+
transition('empty',{},operationId);
|
|
671
726
|
return true;
|
|
672
727
|
}
|
|
673
728
|
|
|
@@ -680,7 +735,8 @@
|
|
|
680
735
|
{
|
|
681
736
|
characters:markdown.length,
|
|
682
737
|
headings:tableOfContents.map(entry=>({...entry}))
|
|
683
|
-
}
|
|
738
|
+
},
|
|
739
|
+
operationId
|
|
684
740
|
);
|
|
685
741
|
return true;
|
|
686
742
|
}
|
|
@@ -875,22 +931,29 @@
|
|
|
875
931
|
}
|
|
876
932
|
|
|
877
933
|
function clear(){
|
|
878
|
-
|
|
934
|
+
if(destroyed){
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
supersedeLoad('markdown-content-cleared');
|
|
938
|
+
const operationId=`${events.descriptor.instanceId}:clear:${++operationSequence}`;
|
|
879
939
|
currentMarkdown='';
|
|
880
940
|
currentSourceURL=options.sourceURL;
|
|
881
941
|
contentElement.replaceChildren();
|
|
882
942
|
tableOfContents=[];
|
|
883
943
|
tableOfContentsList.replaceChildren();
|
|
884
|
-
transition('empty');
|
|
944
|
+
transition('empty',{},operationId);
|
|
885
945
|
return true;
|
|
886
946
|
}
|
|
887
947
|
|
|
888
948
|
function fail(error){
|
|
889
|
-
|
|
890
|
-
|
|
949
|
+
if(destroyed){
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
supersedeLoad('markdown-failure-presented');
|
|
953
|
+
return showError(error,`${events.descriptor.instanceId}:fail:${++operationSequence}`);
|
|
891
954
|
}
|
|
892
955
|
|
|
893
|
-
function showError(error){
|
|
956
|
+
function showError(error,operationId=null){
|
|
894
957
|
currentMarkdown='';
|
|
895
958
|
contentElement.replaceChildren();
|
|
896
959
|
tableOfContents=[];
|
|
@@ -898,11 +961,16 @@
|
|
|
898
961
|
const message=error instanceof Error
|
|
899
962
|
?error.message
|
|
900
963
|
:String(error||options.labels.error);
|
|
901
|
-
|
|
964
|
+
const code=String(error?.code||'ARCANE_MARKDOWN_DOCUMENT_RENDER_FAILED');
|
|
965
|
+
const reason=String(error?.reason||'markdown-document-render-failed');
|
|
966
|
+
transition('error',{message:message.slice(0,2048),code,reason},operationId);
|
|
902
967
|
return false;
|
|
903
968
|
}
|
|
904
969
|
|
|
905
|
-
function transition(nextState,detail={}){
|
|
970
|
+
function transition(nextState,detail={},operationId=null){
|
|
971
|
+
if(destroyed){
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
906
974
|
state=nextState;
|
|
907
975
|
updateStateDOM();
|
|
908
976
|
|
|
@@ -913,12 +981,19 @@
|
|
|
913
981
|
state
|
|
914
982
|
}
|
|
915
983
|
);
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
)
|
|
984
|
+
const publicDetail={
|
|
985
|
+
characters:Number(detail.characters||currentMarkdown.length||0),
|
|
986
|
+
headingCount:Array.isArray(detail.headings)?detail.headings.length:tableOfContents.length,
|
|
987
|
+
state
|
|
988
|
+
};
|
|
989
|
+
if(detail.code){
|
|
990
|
+
publicDetail.code=String(detail.code);
|
|
991
|
+
}
|
|
992
|
+
if(detail.reason){
|
|
993
|
+
publicDetail.reason=String(detail.reason);
|
|
994
|
+
}
|
|
995
|
+
const statePublication=events.dispatch('markdown-document-state',eventDetail,{publicDetail,operationId});
|
|
996
|
+
projectArcaneDOMEvent(host,statePublication.occurrence,{bubbles:true,composed:true});
|
|
922
997
|
|
|
923
998
|
const stateEvent={
|
|
924
999
|
empty:'markdown-document-empty',
|
|
@@ -927,12 +1002,8 @@
|
|
|
927
1002
|
ready:'markdown-document-rendered'
|
|
928
1003
|
}[state];
|
|
929
1004
|
if(stateEvent){
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
stateEvent,
|
|
933
|
-
{bubbles:true,composed:true,detail:eventDetail}
|
|
934
|
-
)
|
|
935
|
-
);
|
|
1005
|
+
const exactPublication=events.dispatch(stateEvent,eventDetail,{publicDetail,operationId});
|
|
1006
|
+
projectArcaneDOMEvent(host,exactPublication.occurrence,{bubbles:true,composed:true});
|
|
936
1007
|
}
|
|
937
1008
|
return eventDetail;
|
|
938
1009
|
}
|
|
@@ -961,6 +1032,9 @@
|
|
|
961
1032
|
}
|
|
962
1033
|
|
|
963
1034
|
function focusDocument(){
|
|
1035
|
+
if(destroyed){
|
|
1036
|
+
return false;
|
|
1037
|
+
}
|
|
964
1038
|
if(state==='ready'){
|
|
965
1039
|
const heading=contentElement.querySelector('h1,h2,h3,h4,h5,h6');
|
|
966
1040
|
(heading||contentElement).focus({preventScroll:true});
|
|
@@ -971,7 +1045,7 @@
|
|
|
971
1045
|
}
|
|
972
1046
|
|
|
973
1047
|
function handleNavigation(event){
|
|
974
|
-
if(event.defaultPrevented||event.button!==0){
|
|
1048
|
+
if(destroyed||event.defaultPrevented||event.button!==0){
|
|
975
1049
|
return;
|
|
976
1050
|
}
|
|
977
1051
|
const link=event.composedPath().find(
|
|
@@ -990,16 +1064,14 @@
|
|
|
990
1064
|
targetURL:link.dataset.markdownTargetUrl||link.href
|
|
991
1065
|
}
|
|
992
1066
|
);
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
);
|
|
1002
|
-
const continueNavigation=host.dispatchEvent(navigationEvent);
|
|
1067
|
+
operationSequence+=1;
|
|
1068
|
+
const operationId=`${events.descriptor.instanceId}:navigate:${operationSequence}`;
|
|
1069
|
+
const publication=events.dispatch('markdown-document-navigate',detail,{
|
|
1070
|
+
operationId,
|
|
1071
|
+
publicDetail:{kind:detail.kind,hasFragment:Boolean(detail.fragment)},
|
|
1072
|
+
cancelable:true
|
|
1073
|
+
});
|
|
1074
|
+
const continueNavigation=publication.accepted&&projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true,cancelable:true});
|
|
1003
1075
|
|
|
1004
1076
|
if(detail.kind==='fragment'||detail.kind==='markdown'){
|
|
1005
1077
|
event.preventDefault();
|
|
@@ -1012,13 +1084,20 @@
|
|
|
1012
1084
|
if(detail.kind==='fragment'){
|
|
1013
1085
|
focusFragment(detail.fragment);
|
|
1014
1086
|
}else if(detail.kind==='markdown'&&options.onNavigate){
|
|
1015
|
-
Promise.resolve(options.onNavigate(detail)).catch(
|
|
1016
|
-
error=>
|
|
1087
|
+
Promise.resolve(options.onNavigate(detail,{signal:lifecycleController.signal,operationId})).catch(
|
|
1088
|
+
error=>{
|
|
1089
|
+
if(!destroyed){
|
|
1090
|
+
console.error('Markdown document navigation callback failed.',error);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1017
1093
|
);
|
|
1018
1094
|
}
|
|
1019
1095
|
}
|
|
1020
1096
|
|
|
1021
1097
|
function focusFragment(fragment=''){
|
|
1098
|
+
if(destroyed){
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1022
1101
|
const decoded=markdownDocumentDecodeFragment(fragment);
|
|
1023
1102
|
const generatedIdentifier=markdownDocumentHeadingBase(decoded);
|
|
1024
1103
|
const target=Array.from(contentElement.querySelectorAll('[id]')).find(
|
|
@@ -1032,17 +1111,31 @@
|
|
|
1032
1111
|
return true;
|
|
1033
1112
|
}
|
|
1034
1113
|
|
|
1114
|
+
function supersedeLoad(reason){
|
|
1115
|
+
loadSequence+=1;
|
|
1116
|
+
loadController?.abort(reason);
|
|
1117
|
+
loadController=null;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function destroy(){
|
|
1121
|
+
if(destroyed){
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
destroyed=true;
|
|
1125
|
+
supersedeLoad('markdown-document-destroyed');
|
|
1126
|
+
lifecycleController.abort('markdown-document-destroyed');
|
|
1127
|
+
currentMarkdown='';
|
|
1128
|
+
tableOfContents=[];
|
|
1129
|
+
contentElement.replaceChildren();
|
|
1130
|
+
tableOfContentsList.replaceChildren();
|
|
1131
|
+
events.dispose();
|
|
1132
|
+
host.ready=false;
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1035
1136
|
renderLabels();
|
|
1036
1137
|
host.ready=true;
|
|
1037
1138
|
transition('empty');
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
'markdown-document-ready',
|
|
1041
|
-
{
|
|
1042
|
-
bubbles:true,
|
|
1043
|
-
composed:true,
|
|
1044
|
-
detail:{component:host,state}
|
|
1045
|
-
}
|
|
1046
|
-
)
|
|
1047
|
-
);
|
|
1139
|
+
const readyPublication=events.dispatch('markdown-document-ready',{component:host,state},{publicDetail:{ready:true,state}});
|
|
1140
|
+
projectArcaneDOMEvent(host,readyPublication.occurrence,{bubbles:true,composed:true});
|
|
1048
1141
|
</script>
|