arcane-os 0.44.0 → 0.46.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 +42 -0
- package/README.md +1 -1
- package/docs/reference/runtime-components.md +78 -7
- package/docs/reference/runtime-modules.md +139 -7
- package/package.json +2 -2
- package/runtime/arcane/components/chat.html +27 -6
- package/runtime/arcane/components/file-manager.html +146 -8
- package/runtime/arcane/components/markdown-document.html +31 -4
- package/runtime/arcane/components/markdown-editor.html +113 -4
- package/runtime/arcane/css/theme.css +41 -0
- package/runtime/arcane/entities/Chat.js +6 -4
- package/runtime/arcane/modules/MD.js +14 -0
- package/runtime/arcane/modules/MarkdownMedia.js +239 -0
- package/runtime/arcane/modules/PersistentAIChatSession.js +22 -8
- package/runtime/arcane/modules/PrintView.js +74 -15
|
@@ -1555,6 +1555,8 @@
|
|
|
1555
1555
|
generation,
|
|
1556
1556
|
urls:new Set(),
|
|
1557
1557
|
media:new Set(),
|
|
1558
|
+
markdownMedia:new Set(),
|
|
1559
|
+
markdownStatus:false,
|
|
1558
1560
|
title:`${directory}/${fileName}`,
|
|
1559
1561
|
rendered:null,
|
|
1560
1562
|
printPreparation:null,
|
|
@@ -1660,6 +1662,7 @@
|
|
|
1660
1662
|
status.remove();
|
|
1661
1663
|
content.append(rendered);
|
|
1662
1664
|
preview.rendered=rendered;
|
|
1665
|
+
updateMarkdownPreviewStatus(preview);
|
|
1663
1666
|
void preparePreviewPrint(preview).catch(function reportPrintPreparation(error){
|
|
1664
1667
|
reportPreviewPrintFailure(error,preview);
|
|
1665
1668
|
});
|
|
@@ -1683,11 +1686,12 @@
|
|
|
1683
1686
|
}
|
|
1684
1687
|
const view=createPrintView({
|
|
1685
1688
|
host,
|
|
1686
|
-
content:()=>preview
|
|
1689
|
+
content:()=>previewPrintContent(preview),
|
|
1687
1690
|
title:()=>preview.title,
|
|
1688
1691
|
active:()=>isCurrentPreview(preview)&&fileModal.opened===true,
|
|
1689
1692
|
priority:1,
|
|
1690
1693
|
signal:preview.controller.signal,
|
|
1694
|
+
prepare:function preparePreviewMedia(signal){return waitForPreviewMarkdown(preview,signal);},
|
|
1691
1695
|
retain:()=>retainPreviewPrintResources(preview),
|
|
1692
1696
|
onError:error=>reportPreviewPrintFailure(error,preview)
|
|
1693
1697
|
});
|
|
@@ -1703,20 +1707,93 @@
|
|
|
1703
1707
|
if(!preview?.rendered||!isCurrentPreview(preview)||fileModal.opened!==true){
|
|
1704
1708
|
return false;
|
|
1705
1709
|
}
|
|
1710
|
+
if(!await waitForPreviewMarkdown(preview)){
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1706
1713
|
const view=await preparePreviewPrint(preview);
|
|
1707
1714
|
return view&&isCurrentPreview(preview)?view.print():false;
|
|
1708
1715
|
}
|
|
1709
1716
|
|
|
1717
|
+
async function waitForPreviewMarkdown(preview,signal){
|
|
1718
|
+
signal?.throwIfAborted();
|
|
1719
|
+
const results=await Promise.allSettled(
|
|
1720
|
+
[...preview.markdownMedia].map(function markdownReadiness(media){
|
|
1721
|
+
return media.owner.ready;
|
|
1722
|
+
})
|
|
1723
|
+
);
|
|
1724
|
+
signal?.throwIfAborted();
|
|
1725
|
+
if(!isCurrentPreview(preview)||fileModal.opened!==true){
|
|
1726
|
+
return false;
|
|
1727
|
+
}
|
|
1728
|
+
const failures=results.filter(function failedMarkdown(result){
|
|
1729
|
+
return result.status==='rejected';
|
|
1730
|
+
}).map(function markdownFailure(result){
|
|
1731
|
+
return result.reason;
|
|
1732
|
+
});
|
|
1733
|
+
if(failures.length){
|
|
1734
|
+
throw new AggregateError(failures,'Local images could not be prepared for printing.');
|
|
1735
|
+
}
|
|
1736
|
+
return true;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
function previewPrintContent(preview){
|
|
1740
|
+
const failures=[];
|
|
1741
|
+
for(const media of preview.markdownMedia){
|
|
1742
|
+
if(!media.settled){
|
|
1743
|
+
throw new Error('Local images are still loading. Use Print after they finish.');
|
|
1744
|
+
}
|
|
1745
|
+
if(media.error){
|
|
1746
|
+
failures.push(media.error);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
if(failures.length){
|
|
1750
|
+
throw new AggregateError(failures,'Local images could not be prepared for printing.');
|
|
1751
|
+
}
|
|
1752
|
+
return preview.rendered;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function updateMarkdownPreviewStatus(preview){
|
|
1756
|
+
if(!isCurrentPreview(preview)||!preview.rendered){
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
if([...preview.markdownMedia].some(function failedMarkdown(media){return media.error;})){
|
|
1760
|
+
preview.markdownStatus=true;
|
|
1761
|
+
preview.printStatus.textContent='Some images could not be loaded. Reopen this file to try again.';
|
|
1762
|
+
preview.printStatus.setAttribute('role','alert');
|
|
1763
|
+
preview.container.append(preview.printStatus);
|
|
1764
|
+
}else if(preview.markdownStatus&&[...preview.markdownMedia].every(function settledMarkdown(media){return media.settled;})){
|
|
1765
|
+
preview.markdownStatus=false;
|
|
1766
|
+
preview.printStatus.remove();
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1710
1770
|
function reportPreviewPrintFailure(error,preview){
|
|
1711
1771
|
arcaneLogging.error('Unable to print file preview:',error);
|
|
1712
1772
|
if(isCurrentPreview(preview)){
|
|
1713
|
-
|
|
1773
|
+
const media=[...preview.markdownMedia];
|
|
1774
|
+
preview.markdownStatus=media.some(function unavailableImage(item){return !item.settled||item.error;});
|
|
1775
|
+
preview.printStatus.textContent=media.some(function pendingImage(item){return !item.settled;})
|
|
1776
|
+
?'Images are still loading. Close this print dialog and use Print after they finish.'
|
|
1777
|
+
:media.some(function failedImage(item){return item.error;})
|
|
1778
|
+
?'Some images could not be loaded. Close any print dialog and reopen this file to try again.'
|
|
1779
|
+
:'Unable to open print preview. Please try again.';
|
|
1714
1780
|
preview.printStatus.setAttribute('role','alert');
|
|
1715
1781
|
preview.container.append(preview.printStatus);
|
|
1716
1782
|
}
|
|
1717
1783
|
}
|
|
1718
1784
|
|
|
1719
1785
|
function retainPreviewPrintResources(preview){
|
|
1786
|
+
const markdownReleases=[];
|
|
1787
|
+
try{
|
|
1788
|
+
for(const media of preview.markdownMedia){
|
|
1789
|
+
markdownReleases.push(media.owner.retain());
|
|
1790
|
+
}
|
|
1791
|
+
}catch(error){
|
|
1792
|
+
for(const release of markdownReleases){
|
|
1793
|
+
release();
|
|
1794
|
+
}
|
|
1795
|
+
throw error;
|
|
1796
|
+
}
|
|
1720
1797
|
preview.printRetains+=1;
|
|
1721
1798
|
let retained=true;
|
|
1722
1799
|
return function releasePrintedPreviewResources(){
|
|
@@ -1724,6 +1801,9 @@
|
|
|
1724
1801
|
return;
|
|
1725
1802
|
}
|
|
1726
1803
|
retained=false;
|
|
1804
|
+
for(const release of markdownReleases){
|
|
1805
|
+
release();
|
|
1806
|
+
}
|
|
1727
1807
|
preview.printRetains-=1;
|
|
1728
1808
|
if(preview.released&&preview.printRetains===0){
|
|
1729
1809
|
releasePreviewURLs(preview);
|
|
@@ -1760,6 +1840,10 @@
|
|
|
1760
1840
|
}
|
|
1761
1841
|
preview.released=true;
|
|
1762
1842
|
preview.controller.abort(reason);
|
|
1843
|
+
for(const media of preview.markdownMedia){
|
|
1844
|
+
media.owner.destroy();
|
|
1845
|
+
}
|
|
1846
|
+
preview.markdownMedia.clear();
|
|
1763
1847
|
for(const element of preview.media){
|
|
1764
1848
|
if(element.localName==='audio'){
|
|
1765
1849
|
element.pause();
|
|
@@ -1817,10 +1901,32 @@
|
|
|
1817
1901
|
}
|
|
1818
1902
|
const body=document.createElement(descriptor.kind==='html'?'iframe':'div');
|
|
1819
1903
|
if(descriptor.kind==='markdown'){
|
|
1820
|
-
markdownModulePromise=markdownModulePromise||
|
|
1821
|
-
|
|
1904
|
+
markdownModulePromise=markdownModulePromise||Promise.all([
|
|
1905
|
+
import('../modules/MD.js'),
|
|
1906
|
+
import('../modules/MarkdownMedia.js')
|
|
1907
|
+
]);
|
|
1908
|
+
const [{default:MD},{hydrateMarkdownMedia}]=await markdownModulePromise;
|
|
1822
1909
|
preview.controller.signal.throwIfAborted();
|
|
1823
|
-
|
|
1910
|
+
const template=document.createElement('template');
|
|
1911
|
+
template.innerHTML=new MD(descriptor.content).safeRendered;
|
|
1912
|
+
const owner=hydrateMarkdownMedia(template.content,{signal:preview.controller.signal});
|
|
1913
|
+
const media={owner,settled:false,error:null};
|
|
1914
|
+
preview.markdownMedia.add(media);
|
|
1915
|
+
void owner.ready.then(
|
|
1916
|
+
function markdownImagesReady(){
|
|
1917
|
+
media.settled=true;
|
|
1918
|
+
updateMarkdownPreviewStatus(preview);
|
|
1919
|
+
},
|
|
1920
|
+
function markdownImagesFailed(error){
|
|
1921
|
+
media.settled=true;
|
|
1922
|
+
media.error=error;
|
|
1923
|
+
if(isCurrentPreview(preview)){
|
|
1924
|
+
arcaneLogging.error('Unable to load local Markdown images:',error);
|
|
1925
|
+
updateMarkdownPreviewStatus(preview);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
);
|
|
1929
|
+
body.append(template.content);
|
|
1824
1930
|
}else{
|
|
1825
1931
|
body.title=descriptor.title||'HTML preview';
|
|
1826
1932
|
body.srcdoc=descriptor.content;
|
|
@@ -2036,8 +2142,9 @@
|
|
|
2036
2142
|
const rendered=document.createElement('div');
|
|
2037
2143
|
rendered.className='file-preview';
|
|
2038
2144
|
let value=fileRef.parsed;
|
|
2039
|
-
const
|
|
2040
|
-
|
|
2145
|
+
const jsonFile=['json','jsonl','ndjson'].includes(fileRef.ext);
|
|
2146
|
+
const transformed=jsonFile&&is.function(host.previewTransform);
|
|
2147
|
+
let completeJSON;
|
|
2041
2148
|
|
|
2042
2149
|
if(transformed){
|
|
2043
2150
|
value=await host.previewTransform(
|
|
@@ -2049,6 +2156,37 @@
|
|
|
2049
2156
|
}
|
|
2050
2157
|
);
|
|
2051
2158
|
preview.controller.signal.throwIfAborted();
|
|
2159
|
+
}else if(jsonFile){
|
|
2160
|
+
// Recognition needs the complete file: FileEntity's parsed JSONL
|
|
2161
|
+
// rows do not include every unparseable line in the original.
|
|
2162
|
+
const [{decodeMarkdownMediaRecord},text]=await Promise.all([
|
|
2163
|
+
import('../modules/MarkdownMedia.js'),
|
|
2164
|
+
fileRef.text()
|
|
2165
|
+
]);
|
|
2166
|
+
preview.controller.signal.throwIfAborted();
|
|
2167
|
+
completeJSON=text;
|
|
2168
|
+
let imageRecord;
|
|
2169
|
+
try {
|
|
2170
|
+
imageRecord = JSON.parse(completeJSON);
|
|
2171
|
+
} catch {
|
|
2172
|
+
// Mixed JSONL and ordinary text keep their complete presentation.
|
|
2173
|
+
imageRecord = null;
|
|
2174
|
+
}
|
|
2175
|
+
while (is.array(imageRecord) && imageRecord.length === 1) {
|
|
2176
|
+
imageRecord = imageRecord[0];
|
|
2177
|
+
}
|
|
2178
|
+
// A dataUrl property alone does not identify an image record.
|
|
2179
|
+
// Recognize its declared media type before attempting any decoding.
|
|
2180
|
+
if (is.string(imageRecord?.mediaType)
|
|
2181
|
+
&& imageRecord.mediaType.toLowerCase().startsWith('image/')) {
|
|
2182
|
+
const image = decodeMarkdownMediaRecord(imageRecord);
|
|
2183
|
+
if (image?.type.startsWith('image/')) {
|
|
2184
|
+
return renderDescriptor(
|
|
2185
|
+
{kind: 'image', content: image, alt: fileRef.name},
|
|
2186
|
+
preview
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2052
2190
|
}
|
|
2053
2191
|
|
|
2054
2192
|
switch(fileRef.ext){
|
|
@@ -2062,7 +2200,7 @@
|
|
|
2062
2200
|
case 'jsonl':
|
|
2063
2201
|
case 'ndjson':{
|
|
2064
2202
|
if(!transformed){
|
|
2065
|
-
rendered.append(createPreformatted(
|
|
2203
|
+
rendered.append(createPreformatted(completeJSON));
|
|
2066
2204
|
break;
|
|
2067
2205
|
}
|
|
2068
2206
|
const records=is.array(value)?value:[];
|
|
@@ -219,7 +219,10 @@
|
|
|
219
219
|
const host=this;
|
|
220
220
|
host.ready=false;
|
|
221
221
|
|
|
222
|
-
const {default:MD}=await
|
|
222
|
+
const [{default:MD},{hydrateMarkdownMedia}]=await Promise.all([
|
|
223
|
+
import('../modules/MD.js'),
|
|
224
|
+
import('../modules/MarkdownMedia.js')
|
|
225
|
+
]);
|
|
223
226
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
224
227
|
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
228
|
const lifecycleController=new AbortController();
|
|
@@ -331,6 +334,7 @@
|
|
|
331
334
|
content:'Document content',
|
|
332
335
|
empty:'No document content is available.',
|
|
333
336
|
error:'Unable to display this document.',
|
|
337
|
+
mediaError:'Some images could not be displayed.',
|
|
334
338
|
loading:'Loading document…',
|
|
335
339
|
tableOfContents:'On this page'
|
|
336
340
|
},
|
|
@@ -348,6 +352,8 @@
|
|
|
348
352
|
let operationSequence=0;
|
|
349
353
|
let loadController=null;
|
|
350
354
|
let destroyed=false;
|
|
355
|
+
let renderedMedia=null;
|
|
356
|
+
let mediaError=false;
|
|
351
357
|
|
|
352
358
|
host.clear=clear;
|
|
353
359
|
host.configure=configure;
|
|
@@ -393,7 +399,7 @@
|
|
|
393
399
|
}
|
|
394
400
|
|
|
395
401
|
const labels={...previous.labels};
|
|
396
|
-
for(const name of ['content','empty','error','loading','tableOfContents']){
|
|
402
|
+
for(const name of ['content','empty','error','mediaError','loading','tableOfContents']){
|
|
397
403
|
if(labelInput[name]!==undefined){
|
|
398
404
|
if(!is.string(labelInput[name])||!labelInput[name].trim()){
|
|
399
405
|
throw new TypeError(`Markdown document labels.${name} must be a nonempty string.`);
|
|
@@ -607,6 +613,7 @@
|
|
|
607
613
|
|
|
608
614
|
currentMarkdown=markdown;
|
|
609
615
|
currentSourceURL=sourceURL;
|
|
616
|
+
releaseRenderedMedia();
|
|
610
617
|
contentElement.replaceChildren();
|
|
611
618
|
tableOfContents=[];
|
|
612
619
|
tableOfContentsList.replaceChildren();
|
|
@@ -631,6 +638,16 @@
|
|
|
631
638
|
|
|
632
639
|
tableOfContents=decorateHeadings(fragment);
|
|
633
640
|
decorateTables(fragment);
|
|
641
|
+
// Resolve local references before connecting their IMG elements. The
|
|
642
|
+
// complete Markdown and ordinary external URLs remain unchanged.
|
|
643
|
+
const media=hydrateMarkdownMedia(fragment,{signal:lifecycleController.signal});
|
|
644
|
+
renderedMedia=media;
|
|
645
|
+
media.ready.catch(function reportMarkdownMediaFailure(error){
|
|
646
|
+
if(destroyed||renderedMedia!==media) return;
|
|
647
|
+
mediaError=true;
|
|
648
|
+
arcaneLogging.error('Markdown document image display failed.',error);
|
|
649
|
+
updateStateDOM();
|
|
650
|
+
});
|
|
634
651
|
contentElement.replaceChildren(fragment);
|
|
635
652
|
buildTableOfContents(tableOfContents);
|
|
636
653
|
transition(
|
|
@@ -644,6 +661,13 @@
|
|
|
644
661
|
return true;
|
|
645
662
|
}
|
|
646
663
|
|
|
664
|
+
function releaseRenderedMedia(){
|
|
665
|
+
const previous=renderedMedia;
|
|
666
|
+
renderedMedia=null;
|
|
667
|
+
mediaError=false;
|
|
668
|
+
previous?.destroy();
|
|
669
|
+
}
|
|
670
|
+
|
|
647
671
|
function renderedMarkdownFragment(html='',sourceURL=options.sourceURL){
|
|
648
672
|
const template=document.createElement('template');
|
|
649
673
|
template.innerHTML=html;
|
|
@@ -764,6 +788,7 @@
|
|
|
764
788
|
const operationId=`${events.descriptor.instanceId}:clear:${++operationSequence}`;
|
|
765
789
|
currentMarkdown='';
|
|
766
790
|
currentSourceURL=options.sourceURL;
|
|
791
|
+
releaseRenderedMedia();
|
|
767
792
|
contentElement.replaceChildren();
|
|
768
793
|
tableOfContents=[];
|
|
769
794
|
tableOfContentsList.replaceChildren();
|
|
@@ -781,6 +806,7 @@
|
|
|
781
806
|
|
|
782
807
|
function showError(error,operationId=null){
|
|
783
808
|
currentMarkdown='';
|
|
809
|
+
releaseRenderedMedia();
|
|
784
810
|
contentElement.replaceChildren();
|
|
785
811
|
tableOfContents=[];
|
|
786
812
|
tableOfContentsList.replaceChildren();
|
|
@@ -839,7 +865,7 @@
|
|
|
839
865
|
host.setAttribute('aria-busy',String(loadingState));
|
|
840
866
|
host.dataset.state=state;
|
|
841
867
|
statusElement.dataset.state=state;
|
|
842
|
-
statusElement.hidden=readyState;
|
|
868
|
+
statusElement.hidden=readyState&&!mediaError;
|
|
843
869
|
contentElement.hidden=!readyState;
|
|
844
870
|
tableOfContentsElement.hidden=(
|
|
845
871
|
!readyState
|
|
@@ -851,7 +877,7 @@
|
|
|
851
877
|
empty:options.labels.empty,
|
|
852
878
|
error:options.labels.error,
|
|
853
879
|
loading:options.labels.loading,
|
|
854
|
-
ready:''
|
|
880
|
+
ready:mediaError?options.labels.mediaError:''
|
|
855
881
|
}[state]||'';
|
|
856
882
|
}
|
|
857
883
|
|
|
@@ -948,6 +974,7 @@
|
|
|
948
974
|
lifecycleController.abort('markdown-document-destroyed');
|
|
949
975
|
currentMarkdown='';
|
|
950
976
|
tableOfContents=[];
|
|
977
|
+
releaseRenderedMedia();
|
|
951
978
|
contentElement.replaceChildren();
|
|
952
979
|
tableOfContentsList.replaceChildren();
|
|
953
980
|
events.dispose();
|
|
@@ -63,6 +63,10 @@
|
|
|
63
63
|
margin-right:auto;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
.media-status:empty{
|
|
67
|
+
display:none;
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
.markdown-editor.fit{
|
|
67
71
|
height:100%;
|
|
68
72
|
min-height:0;
|
|
@@ -118,6 +122,7 @@
|
|
|
118
122
|
<textarea id="entryMarkdown" class="entry-markdown" part="input"></textarea>
|
|
119
123
|
<section class="actions" part="actions">
|
|
120
124
|
<span id="status" class="status" part="status" aria-live="polite"></span>
|
|
125
|
+
<span id="mediaStatus" class="media-status" part="media-status" aria-live="polite"></span>
|
|
121
126
|
<button id="print" part="print-action" type="button">Print</button>
|
|
122
127
|
<button id="save" part="save-action" type="button" disabled></button>
|
|
123
128
|
</section>
|
|
@@ -130,7 +135,10 @@
|
|
|
130
135
|
const {arcaneLogging}=await import('arcane-os/logging');
|
|
131
136
|
|
|
132
137
|
const host=this;
|
|
133
|
-
const {default:MD}=await
|
|
138
|
+
const [{default:MD},{hydrateMarkdownMedia}]=await Promise.all([
|
|
139
|
+
import('../modules/MD.js'),
|
|
140
|
+
import('../modules/MarkdownMedia.js')
|
|
141
|
+
]);
|
|
134
142
|
const {
|
|
135
143
|
applyMarkdownFormat,
|
|
136
144
|
normalizeMarkdownOptions
|
|
@@ -146,9 +154,12 @@
|
|
|
146
154
|
const saveButton=host.shadowRoot.querySelector('#save');
|
|
147
155
|
const printButton=host.shadowRoot.querySelector('#print');
|
|
148
156
|
const status=host.shadowRoot.querySelector('#status');
|
|
157
|
+
const mediaStatus=host.shadowRoot.querySelector('#mediaStatus');
|
|
149
158
|
|
|
150
159
|
let previewFrame=0;
|
|
151
160
|
let previewFollowPending=false;
|
|
161
|
+
let previewMedia=null;
|
|
162
|
+
let printMediaRetains=null;
|
|
152
163
|
let operationSequence=0;
|
|
153
164
|
let saveGeneration=0;
|
|
154
165
|
let saveController=null;
|
|
@@ -166,6 +177,8 @@
|
|
|
166
177
|
title:()=>titleInput.value,
|
|
167
178
|
active:()=>!destroyed&&host.isConnected&&host.getClientRects().length>0,
|
|
168
179
|
signal:lifecycleController.signal,
|
|
180
|
+
retain:retainPrintMedia,
|
|
181
|
+
prepare:preparePrintMedia,
|
|
169
182
|
onError:reportPrintFailure
|
|
170
183
|
});
|
|
171
184
|
}
|
|
@@ -401,7 +414,41 @@
|
|
|
401
414
|
const scrollTop=preview.scrollTop;
|
|
402
415
|
const scrollLeft=preview.scrollLeft;
|
|
403
416
|
const markdown=new MD(textarea.value,{sourceMap:options.followPreview});
|
|
404
|
-
|
|
417
|
+
const template=document.createElement('template');
|
|
418
|
+
template.innerHTML=markdown.safeRendered;
|
|
419
|
+
const pending=[...template.content.querySelectorAll('img')].some(
|
|
420
|
+
image=>image.getAttribute('src')?.startsWith('arcane-media:')
|
|
421
|
+
);
|
|
422
|
+
// Hydration removes custom src values before these nodes join the live DOM.
|
|
423
|
+
const handle=hydrateMarkdownMedia(template.content,{signal:lifecycleController.signal});
|
|
424
|
+
const media={handle,pending,error:null};
|
|
425
|
+
const previous=previewMedia;
|
|
426
|
+
previewMedia=media;
|
|
427
|
+
if(printMediaRetains){
|
|
428
|
+
// The print owner may still be awaiting its final current snapshot.
|
|
429
|
+
printMediaRetains.add(handle.retain());
|
|
430
|
+
}
|
|
431
|
+
previous?.handle.destroy();
|
|
432
|
+
mediaStatus.innerText=pending?'Loading saved images…':'';
|
|
433
|
+
media.ready=handle.ready.then(function mediaReady(){
|
|
434
|
+
media.pending=false;
|
|
435
|
+
if(!destroyed&&previewMedia===media){
|
|
436
|
+
mediaStatus.innerText='';
|
|
437
|
+
}
|
|
438
|
+
},function mediaFailed(error){
|
|
439
|
+
media.pending=false;
|
|
440
|
+
media.error=error;
|
|
441
|
+
const cancelled=error?.name==='AbortError'||error?.failures?.every(
|
|
442
|
+
failure=>failure.reason?.name==='AbortError'
|
|
443
|
+
);
|
|
444
|
+
if(!cancelled){
|
|
445
|
+
arcaneLogging.error('Unable to display saved Markdown images:',error);
|
|
446
|
+
}
|
|
447
|
+
if(!destroyed&&previewMedia===media&&!cancelled){
|
|
448
|
+
mediaStatus.innerText='Some saved images could not be displayed.';
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
preview.replaceChildren(template.content);
|
|
405
452
|
preview.querySelectorAll('a').forEach(
|
|
406
453
|
link=>{
|
|
407
454
|
link.target='_blank';
|
|
@@ -589,9 +636,38 @@
|
|
|
589
636
|
}
|
|
590
637
|
|
|
591
638
|
function renderedPrintContent(){
|
|
639
|
+
if(previewFrame){
|
|
640
|
+
updatePreview(false);
|
|
641
|
+
}
|
|
642
|
+
if(previewMedia?.pending){
|
|
643
|
+
const error=new Error('Images are still loading. Print again when they finish.');
|
|
644
|
+
error.code='ARCANE_MARKDOWN_MEDIA_PENDING';
|
|
645
|
+
throw error;
|
|
646
|
+
}
|
|
647
|
+
if(previewMedia?.error){
|
|
648
|
+
throw previewMedia.error;
|
|
649
|
+
}
|
|
592
650
|
return preview;
|
|
593
651
|
}
|
|
594
652
|
|
|
653
|
+
function retainPrintMedia(){
|
|
654
|
+
renderedPrintContent();
|
|
655
|
+
const releases=new Set();
|
|
656
|
+
if(previewMedia){
|
|
657
|
+
releases.add(previewMedia.handle.retain());
|
|
658
|
+
}
|
|
659
|
+
printMediaRetains=releases;
|
|
660
|
+
return function releasePrintMedia(){
|
|
661
|
+
if(printMediaRetains===releases){
|
|
662
|
+
printMediaRetains=null;
|
|
663
|
+
}
|
|
664
|
+
for(const release of releases){
|
|
665
|
+
release();
|
|
666
|
+
}
|
|
667
|
+
releases.clear();
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
595
671
|
async function print(){
|
|
596
672
|
if(destroyed){
|
|
597
673
|
return false;
|
|
@@ -600,12 +676,40 @@
|
|
|
600
676
|
if(destroyed||!printView){
|
|
601
677
|
return false;
|
|
602
678
|
}
|
|
603
|
-
if(
|
|
604
|
-
|
|
679
|
+
if(!await preparePrintMedia()){
|
|
680
|
+
return false;
|
|
605
681
|
}
|
|
606
682
|
return printView.print();
|
|
607
683
|
}
|
|
608
684
|
|
|
685
|
+
async function preparePrintMedia(signal){
|
|
686
|
+
let cancel;
|
|
687
|
+
const cancelled=new Promise(function waitForCancellation(resolve){cancel=resolve;});
|
|
688
|
+
signal?.addEventListener('abort',cancel,{once:true});
|
|
689
|
+
try{
|
|
690
|
+
while(!destroyed&&!signal?.aborted){
|
|
691
|
+
if(previewFrame){
|
|
692
|
+
updatePreview(false);
|
|
693
|
+
}
|
|
694
|
+
const media=previewMedia;
|
|
695
|
+
await Promise.race([media.ready,cancelled]);
|
|
696
|
+
if(destroyed||signal?.aborted){
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
if(previewFrame||previewMedia!==media){
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if(media.error){
|
|
703
|
+
throw media.error;
|
|
704
|
+
}
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
return false;
|
|
708
|
+
}finally{
|
|
709
|
+
signal?.removeEventListener('abort',cancel);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
609
713
|
function printFromControl(){
|
|
610
714
|
void print().catch(reportPrintFailure);
|
|
611
715
|
}
|
|
@@ -615,6 +719,10 @@
|
|
|
615
719
|
return;
|
|
616
720
|
}
|
|
617
721
|
arcaneLogging.error('Unable to print rendered Markdown:',error);
|
|
722
|
+
if(error?.code==='ARCANE_MARKDOWN_MEDIA_PENDING'){
|
|
723
|
+
mediaStatus.innerText=error.message;
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
618
726
|
status.innerText='Unable to open print preview. Please try again.';
|
|
619
727
|
}
|
|
620
728
|
|
|
@@ -686,6 +794,7 @@
|
|
|
686
794
|
}
|
|
687
795
|
destroyed=true;
|
|
688
796
|
supersedeSave('markdown-editor-destroyed');
|
|
797
|
+
previewMedia?.handle.destroy();
|
|
689
798
|
lifecycleController.abort('markdown-editor-destroyed');
|
|
690
799
|
previewFollowPending=false;
|
|
691
800
|
if(previewFrame){
|
|
@@ -327,3 +327,44 @@ body{
|
|
|
327
327
|
scroll-behavior:auto !important;
|
|
328
328
|
transition-duration:.01ms !important;
|
|
329
329
|
}
|
|
330
|
+
|
|
331
|
+
/* Paper uses the existing base light palette regardless of the screen skin.
|
|
332
|
+
* Print-only importance also overrides custom-theme inline tokens; no saved
|
|
333
|
+
* preference, screen layout, media paint, or scrolling state changes here.
|
|
334
|
+
*/
|
|
335
|
+
@media print {
|
|
336
|
+
@page {
|
|
337
|
+
margin: 15mm;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
:root,
|
|
341
|
+
body,
|
|
342
|
+
[data-arcane-palette] {
|
|
343
|
+
color-scheme: light !important;
|
|
344
|
+
--background: rgb(255, 255, 255) !important;
|
|
345
|
+
--modal-background: rgb(255, 255, 255) !important;
|
|
346
|
+
--text-color: rgb(23, 34, 56) !important;
|
|
347
|
+
--primary-color: rgb(23, 34, 56) !important;
|
|
348
|
+
--secondary-color: rgb(229, 233, 243) !important;
|
|
349
|
+
--accent-color: var(--focus-color) !important;
|
|
350
|
+
--button-text-color: rgb(255, 255, 255) !important;
|
|
351
|
+
--border-color: rgb(203, 211, 226) !important;
|
|
352
|
+
--focus-color: rgb(118, 87, 213) !important;
|
|
353
|
+
--muted-text-color: rgb(95, 107, 130) !important;
|
|
354
|
+
--success-color: rgb(39, 122, 75) !important;
|
|
355
|
+
--warning-color: rgb(154, 101, 0) !important;
|
|
356
|
+
--danger-color: rgb(165, 56, 56) !important;
|
|
357
|
+
--review-color: rgb(133, 85, 165) !important;
|
|
358
|
+
--shadow-color: rgba(23, 34, 56, .16) !important;
|
|
359
|
+
--platinum: rgb(102, 112, 133) !important;
|
|
360
|
+
--icon-filter: none !important;
|
|
361
|
+
--file-icon-filter: hue-rotate(8deg) saturate(.62) brightness(.86) !important;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
html,
|
|
365
|
+
body {
|
|
366
|
+
margin: 0;
|
|
367
|
+
background-color: var(--modal-background);
|
|
368
|
+
color: var(--text-color);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -391,10 +391,12 @@ class ChatEntity{
|
|
|
391
391
|
fileName='';
|
|
392
392
|
|
|
393
393
|
/**
|
|
394
|
-
* Controls
|
|
394
|
+
* Controls retention of new message additions.
|
|
395
395
|
*
|
|
396
396
|
* When true, any message addition automatically writes
|
|
397
|
-
* the updated message list to OPFS.
|
|
397
|
+
* the updated message list to OPFS. When false, new additions are excluded
|
|
398
|
+
* from the transcript, recurring context, storage, and memory extraction.
|
|
399
|
+
* Existing retained and saved messages remain unchanged.
|
|
398
400
|
*
|
|
399
401
|
* @type {boolean}
|
|
400
402
|
*/
|
|
@@ -1080,7 +1082,7 @@ ${JSON.stringify(transcript)}`
|
|
|
1080
1082
|
if(!records.length){
|
|
1081
1083
|
return false;
|
|
1082
1084
|
}
|
|
1083
|
-
if(!persist) return false;
|
|
1085
|
+
if(!persist||!this.persist) return false;
|
|
1084
1086
|
|
|
1085
1087
|
this.#messages.push(...records);
|
|
1086
1088
|
const durableRecords=storedChatRecords(records);
|
|
@@ -1088,7 +1090,7 @@ ${JSON.stringify(transcript)}`
|
|
|
1088
1090
|
this.#saved=false;
|
|
1089
1091
|
}
|
|
1090
1092
|
|
|
1091
|
-
if(!
|
|
1093
|
+
if(!durableRecords.length){
|
|
1092
1094
|
return;
|
|
1093
1095
|
}
|
|
1094
1096
|
|
|
@@ -6,6 +6,20 @@ const markdownOptions = {
|
|
|
6
6
|
async: false,
|
|
7
7
|
pedantic: false,
|
|
8
8
|
gfm: true,
|
|
9
|
+
tokenizer: {
|
|
10
|
+
html(src) {
|
|
11
|
+
const native = marked.Tokenizer.prototype.html.call(this, src);
|
|
12
|
+
if (native) return native;
|
|
13
|
+
// Quoted image values can contain blank lines that would otherwise
|
|
14
|
+
// split the tag into Markdown paragraphs before inline HTML parsing.
|
|
15
|
+
const image = /^ {0,3}<img(?:\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*\/?>(?=[ \t]*(?:\n|$))/i;
|
|
16
|
+
const match = image.exec(src);
|
|
17
|
+
if (!match) return;
|
|
18
|
+
const quotedValues = match[0].match(/"[^"]*"|'[^']*'/g) || [];
|
|
19
|
+
if (!quotedValues.some(function containsNewline(value) {return value.includes('\n');})) return;
|
|
20
|
+
return {type:'html', block:true, raw:match[0], text:match[0]};
|
|
21
|
+
}
|
|
22
|
+
},
|
|
9
23
|
renderer: {
|
|
10
24
|
link(href, title, text) {
|
|
11
25
|
const link = marked.Renderer.prototype.link.call(this, href, title, text);
|