retold-remote 0.0.3 → 0.0.5

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.
@@ -1732,432 +1732,122 @@ let tmpCode=this.codeJar.toString();this.codeJar.destroy();this.codeJar=this._co
1732
1732
  * Marshal code content from the data address into the view.
1733
1733
  */marshalToView(){super.marshalToView();if(this.codeJar&&this.options.CodeDataAddress){let tmpCode=this._resolveCodeContent();if(typeof tmpCode==='string'){this.codeJar.updateCode(tmpCode);this._updateLineNumbers();}}}/**
1734
1734
  * Marshal the current code content back to the data address.
1735
- */marshalFromView(){super.marshalFromView();if(this.codeJar&&this.options.CodeDataAddress){this.onCodeChange(this.codeJar.toString());}}}module.exports=PictSectionCode;module.exports.default_configuration=_DefaultConfiguration;module.exports.createHighlighter=libCreateHighlighter;},{"./Pict-Code-Highlighter.js":47,"./Pict-Section-Code-DefaultConfiguration.js":48,"pict-view":76}],50:[function(require,module,exports){// The container for all the Pict-Section-Content related code.
1736
- // The main content view class
1737
- module.exports=require('./views/Pict-View-Content.js');// The content provider (markdown parsing, HTML escaping)
1738
- module.exports.PictContentProvider=require('./providers/Pict-Provider-Content.js');},{"./providers/Pict-Provider-Content.js":51,"./views/Pict-View-Content.js":52}],51:[function(require,module,exports){const libPictProvider=require('pict-provider');const libCreateHighlighter=require('pict-section-code').createHighlighter;/**
1739
- * Content Provider for Pict Section Content
1740
- *
1741
- * A general-purpose markdown-to-HTML parser with support for:
1742
- * - Headings, paragraphs, lists, blockquotes, horizontal rules
1743
- * - Fenced code blocks with language tags (nested fence support)
1744
- * - Syntax highlighting and line numbers for code blocks (via pict-section-code)
1745
- * - Tables (GFM pipe syntax)
1746
- * - Mermaid diagram blocks
1747
- * - KaTeX math (inline and display)
1748
- * - Bold, italic, inline code, links, images
1749
- *
1750
- * Link resolution is customizable via an optional callback.
1751
- */class PictContentProvider extends libPictProvider{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);}/**
1752
- * Highlight a code string using pict-section-code's syntax highlighter.
1753
- * Uses a mock element to interface with the highlighter's DOM-based API.
1754
- *
1755
- * @param {string} pCode - The raw code string
1756
- * @param {string} pLanguage - The language identifier (e.g. "javascript", "html")
1757
- * @returns {string} The syntax-highlighted HTML
1758
- */highlightCode(pCode,pLanguage){if(!pCode){return'';}let tmpHighlighter=libCreateHighlighter(pLanguage);// Create a mock element to interface with the highlighter
1759
- let tmpMockElement={textContent:pCode,innerHTML:''};tmpHighlighter(tmpMockElement);return tmpMockElement.innerHTML;}/**
1760
- * Generate line number HTML for a code block.
1761
- *
1762
- * @param {string} pCode - The raw code string
1763
- * @returns {string} HTML string with line number spans
1764
- */generateLineNumbers(pCode){if(!pCode){return'<span>1</span>';}let tmpLineCount=pCode.split('\n').length;let tmpHTML='';for(let i=1;i<=tmpLineCount;i++){tmpHTML+='<span>'+i+'</span>';}return tmpHTML;}/**
1765
- * Parse a markdown string into HTML.
1766
- *
1767
- * @param {string} pMarkdown - The raw markdown text
1768
- * @param {Function} [pLinkResolver] - Optional callback for link resolution: (pHref, pLinkText) => { href, target, rel } or null
1769
- * @returns {string} The parsed HTML
1770
- */parseMarkdown(pMarkdown,pLinkResolver){if(!pMarkdown){return'';}let tmpLines=pMarkdown.split('\n');let tmpHTML=[];let tmpInCodeBlock=false;let tmpCodeFenceLength=0;let tmpCodeLang='';let tmpCodeLines=[];let tmpInList=false;let tmpListType='';let tmpInBlockquote=false;let tmpBlockquoteLines=[];let tmpInMathBlock=false;let tmpMathLines=[];let tmpParagraphLines=[];// Helper to flush accumulated paragraph lines into a single <p> tag
1771
- let fFlushParagraph=()=>{if(tmpParagraphLines.length>0){tmpHTML.push('<p>'+tmpParagraphLines.map(pLine=>{return this.parseInline(pLine,pLinkResolver);}).join(' ')+'</p>');tmpParagraphLines=[];}};for(let i=0;i<tmpLines.length;i++){let tmpLine=tmpLines[i];// Display math blocks ($$...$$) — skip if inside a code block
1772
- if(!tmpInCodeBlock&&tmpLine.trim().match(/^\$\$/)){if(tmpInMathBlock){// End math block
1773
- tmpHTML.push('<div class="pict-content-katex-display">'+tmpMathLines.join('\n')+'</div>');tmpInMathBlock=false;tmpMathLines=[];}else{// Flush any pending paragraph
1774
- fFlushParagraph();// Close any open list or blockquote
1775
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}tmpInMathBlock=true;}continue;}if(tmpInMathBlock){tmpMathLines.push(tmpLine);continue;}// Code blocks (fenced) — track fence length so ````x```` nests around ```y```
1776
- let tmpFenceMatch=tmpLine.match(/^(`{3,})/);if(tmpFenceMatch){let tmpFenceLen=tmpFenceMatch[1].length;if(tmpInCodeBlock){// Only close if the closing fence is at least as long as the opening
1777
- if(tmpFenceLen>=tmpCodeFenceLength&&tmpLine.trim()===tmpFenceMatch[1]){// End code block
1778
- if(tmpCodeLang==='mermaid'){// Mermaid diagrams: output raw content for client-side rendering
1779
- tmpHTML.push('<pre class="mermaid">'+tmpCodeLines.join('\n')+'</pre>');}else{let tmpCodeText=tmpCodeLines.join('\n');let tmpHighlightedCode=this.highlightCode(tmpCodeText,tmpCodeLang);let tmpLineNumbersHTML=this.generateLineNumbers(tmpCodeText);tmpHTML.push('<div class="pict-content-code-wrap"><div class="pict-content-code-line-numbers">'+tmpLineNumbersHTML+'</div><pre><code class="language-'+this.escapeHTML(tmpCodeLang)+'">'+tmpHighlightedCode+'</code></pre></div>');}tmpInCodeBlock=false;tmpCodeFenceLength=0;tmpCodeLang='';tmpCodeLines=[];continue;}else{// Inner fence with fewer backticks — treat as content
1780
- tmpCodeLines.push(tmpLine);continue;}}else{// Flush any pending paragraph
1781
- fFlushParagraph();// Close any open list or blockquote
1782
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}// Start code block — record fence length
1783
- tmpCodeFenceLength=tmpFenceLen;tmpCodeLang=tmpLine.replace(/^`{3,}/,'').trim();tmpInCodeBlock=true;continue;}}if(tmpInCodeBlock){tmpCodeLines.push(tmpLine);continue;}// Blockquotes
1784
- if(tmpLine.match(/^>\s?/)){if(!tmpInBlockquote){// Flush any pending paragraph
1785
- fFlushParagraph();// Close any open list
1786
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}tmpInBlockquote=true;tmpBlockquoteLines=[];}tmpBlockquoteLines.push(tmpLine.replace(/^>\s?/,''));continue;}else if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}// Horizontal rule
1787
- if(tmpLine.match(/^(-{3,}|\*{3,}|_{3,})\s*$/)){fFlushParagraph();if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}tmpHTML.push('<hr>');continue;}// Headings
1788
- let tmpHeadingMatch=tmpLine.match(/^(#{1,6})\s+(.+)/);if(tmpHeadingMatch){fFlushParagraph();if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}let tmpLevel=tmpHeadingMatch[1].length;let tmpText=this.parseInline(tmpHeadingMatch[2],pLinkResolver);let tmpID=tmpHeadingMatch[2].toLowerCase().replace(/[^\w\s-]/g,'').replace(/\s+/g,'-');tmpHTML.push('<h'+tmpLevel+' id="'+tmpID+'">'+tmpText+'</h'+tmpLevel+'>');continue;}// Unordered list items
1789
- let tmpULMatch=tmpLine.match(/^(\s*)[-*+]\s+(.*)/);if(tmpULMatch){fFlushParagraph();if(!tmpInList||tmpListType!=='ul'){if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}tmpHTML.push('<ul>');tmpInList=true;tmpListType='ul';}tmpHTML.push('<li>'+this.parseInline(tmpULMatch[2],pLinkResolver)+'</li>');continue;}// Ordered list items
1790
- let tmpOLMatch=tmpLine.match(/^(\s*)\d+\.\s+(.*)/);if(tmpOLMatch){fFlushParagraph();if(!tmpInList||tmpListType!=='ol'){if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}tmpHTML.push('<ol>');tmpInList=true;tmpListType='ol';}tmpHTML.push('<li>'+this.parseInline(tmpOLMatch[2],pLinkResolver)+'</li>');continue;}// Close list if we've left list items
1791
- if(tmpInList&&tmpLine.trim()!==''){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}// Empty line — flush any accumulated paragraph
1792
- if(tmpLine.trim()===''){fFlushParagraph();continue;}// Table detection
1793
- if(tmpLine.match(/^\|/)&&i+1<tmpLines.length&&tmpLines[i+1].match(/^\|[\s-:|]+\|/)){fFlushParagraph();// Close any open list
1794
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}let tmpTableHTML='<table>';// Header row
1795
- let tmpHeaders=tmpLine.split('|').filter(pCell=>{return pCell.trim()!=='';});tmpTableHTML+='<thead><tr>';for(let h=0;h<tmpHeaders.length;h++){tmpTableHTML+='<th>'+this.parseInline(tmpHeaders[h].trim(),pLinkResolver)+'</th>';}tmpTableHTML+='</tr></thead>';// Skip separator row
1796
- i++;// Body rows
1797
- tmpTableHTML+='<tbody>';while(i+1<tmpLines.length&&tmpLines[i+1].match(/^\|/)){i++;let tmpCells=tmpLines[i].split('|').filter(pCell=>{return pCell.trim()!=='';});tmpTableHTML+='<tr>';for(let c=0;c<tmpCells.length;c++){tmpTableHTML+='<td>'+this.parseInline(tmpCells[c].trim(),pLinkResolver)+'</td>';}tmpTableHTML+='</tr>';}tmpTableHTML+='</tbody></table>';tmpHTML.push(tmpTableHTML);continue;}// Accumulate paragraph lines — consecutive non-blank text lines
1798
- // will be joined into a single <p> tag when flushed
1799
- tmpParagraphLines.push(tmpLine);}// Flush any remaining accumulated paragraph
1800
- fFlushParagraph();// Close any trailing open elements
1801
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver)+'</blockquote>');}if(tmpInCodeBlock){let tmpCodeText=tmpCodeLines.join('\n');let tmpHighlightedCode=this.highlightCode(tmpCodeText,tmpCodeLang);let tmpLineNumbersHTML=this.generateLineNumbers(tmpCodeText);tmpHTML.push('<div class="pict-content-code-wrap"><div class="pict-content-code-line-numbers">'+tmpLineNumbersHTML+'</div><pre><code>'+tmpHighlightedCode+'</code></pre></div>');}return tmpHTML.join('\n');}/**
1802
- * Parse inline markdown elements (bold, italic, code, links, images, KaTeX).
1803
- *
1804
- * @param {string} pText - The text to parse
1805
- * @param {Function} [pLinkResolver] - Optional callback: (pHref, pLinkText) => { href, target, rel } or null
1806
- * @returns {string} HTML with inline elements
1807
- */parseInline(pText,pLinkResolver){if(!pText){return'';}let tmpResult=pText;// Extract inline code spans into placeholders so bold/italic regexes don't mangle their contents
1808
- let tmpCodeSpans=[];tmpResult=tmpResult.replace(/`([^`]+)`/g,(pMatch,pCode)=>{let tmpIndex=tmpCodeSpans.length;tmpCodeSpans.push('<code>'+pCode+'</code>');return'\x00CODEINLINE'+tmpIndex+'\x00';});// Inline LaTeX equations ($...$) — must be processed before other inline patterns
1809
- // Match single $ delimiters that aren't adjacent to spaces (to avoid false positives with currency)
1810
- tmpResult=tmpResult.replace(/\$([^\$\s][^\$]*?[^\$\s])\$/g,'<span class="pict-content-katex-inline">$1</span>');// Also match single-character inline math like $x$
1811
- tmpResult=tmpResult.replace(/\$([^\$\s])\$/g,'<span class="pict-content-katex-inline">$1</span>');// Images
1812
- tmpResult=tmpResult.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,'<img src="$2" alt="$1">');// Links
1813
- tmpResult=tmpResult.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(pMatch,pLinkText,pHref)=>{if(typeof pLinkResolver==='function'){let tmpResolved=pLinkResolver(pHref,pLinkText);if(tmpResolved){let tmpTarget=tmpResolved.target?' target="'+tmpResolved.target+'"':'';let tmpRel=tmpResolved.rel?' rel="'+tmpResolved.rel+'"':'';return'<a href="'+tmpResolved.href+'"'+tmpTarget+tmpRel+'>'+pLinkText+'</a>';}}// Default behavior: external links open in new tab
1814
- if(pHref.match(/^https?:\/\//)){return'<a href="'+pHref+'" target="_blank" rel="noopener">'+pLinkText+'</a>';}return'<a href="'+pHref+'">'+pLinkText+'</a>';});// Bold
1815
- tmpResult=tmpResult.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');tmpResult=tmpResult.replace(/__([^_]+)__/g,'<strong>$1</strong>');// Italic
1816
- tmpResult=tmpResult.replace(/\*([^*]+)\*/g,'<em>$1</em>');tmpResult=tmpResult.replace(/_([^_]+)_/g,'<em>$1</em>');// Restore inline code spans from placeholders
1817
- tmpResult=tmpResult.replace(/\x00CODEINLINE(\d+)\x00/g,(pMatch,pIndex)=>{return tmpCodeSpans[parseInt(pIndex)];});return tmpResult;}/**
1818
- * Escape HTML special characters.
1819
- *
1820
- * @param {string} pText - The text to escape
1821
- * @returns {string} The escaped text
1822
- */escapeHTML(pText){if(!pText){return'';}return pText.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}}module.exports=PictContentProvider;module.exports.default_configuration={ProviderIdentifier:"Pict-Content",AutoInitialize:true,AutoInitializeOrdinal:0};},{"pict-provider":46,"pict-section-code":49}],52:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={ViewIdentifier:"Pict-Content",DefaultRenderable:"Pict-Content-Display",DefaultDestinationAddress:"#Pict-Content-Container",AutoRender:false,CSS:/*css*/`
1823
- .pict-content {
1824
- padding: 2em 3em;
1825
- max-width: 900px;
1826
- margin: 0 auto;
1735
+ */marshalFromView(){super.marshalFromView();if(this.codeJar&&this.options.CodeDataAddress){this.onCodeChange(this.codeJar.toString());}}}module.exports=PictSectionCode;module.exports.default_configuration=_DefaultConfiguration;module.exports.createHighlighter=libCreateHighlighter;},{"./Pict-Code-Highlighter.js":47,"./Pict-Section-Code-DefaultConfiguration.js":48,"pict-view":76}],50:[function(require,module,exports){module.exports={"ViewIdentifier":"Pict-FileBrowser","DefaultRenderable":"FileBrowser-Container","DefaultDestinationAddress":"#Pict-FileBrowser-Container","AutoRender":false,// --- FileBrowser State ---
1736
+ // These are the four core state values for the file browser.
1737
+ // They live in AppData at the addresses below.
1738
+ "StateAddresses":{"Layout":"AppData.PictFileBrowser.Layout","RootLocation":"AppData.PictFileBrowser.RootLocation","CurrentLocation":"AppData.PictFileBrowser.CurrentLocation","CurrentFile":"AppData.PictFileBrowser.CurrentFile"},// Default state values
1739
+ "DefaultState":{"Layout":"browser-detail","RootLocation":"/","CurrentLocation":"","CurrentFile":null},// --- File Data ---
1740
+ // The consuming application must populate this address with file/folder data.
1741
+ // Expected format: Array of objects with at minimum { Name, Type }
1742
+ // Type: "folder" or "file"
1743
+ // Additional properties: Size, Modified, Extension, MimeType, Path, Icon, ThumbnailURL
1744
+ "FileListAddress":"AppData.PictFileBrowser.FileList","FolderTreeAddress":"AppData.PictFileBrowser.FolderTree","ChildFolderCacheAddress":"AppData.PictFileBrowser.ChildFolderCache",// --- Templates ---
1745
+ "Templates":[{"Hash":"FileBrowser-Container-Template","Template":/*html*/`
1746
+ <div class="pict-filebrowser" id="Pict-FileBrowser-Wrap">
1747
+ <div class="pict-filebrowser-browse-pane" id="Pict-FileBrowser-BrowsePane"></div>
1748
+ <div class="pict-filebrowser-main-pane">
1749
+ <div class="pict-filebrowser-list-pane" id="Pict-FileBrowser-ListPane"></div>
1750
+ <div class="pict-filebrowser-view-pane" id="Pict-FileBrowser-ViewPane"></div>
1751
+ </div>
1752
+ </div>
1753
+ `}],"Renderables":[{"RenderableHash":"FileBrowser-Container","TemplateHash":"FileBrowser-Container-Template","DestinationAddress":"#Pict-FileBrowser-Container","RenderMethod":"replace"}],// --- CSS ---
1754
+ "CSS":/*css*/`
1755
+ .pict-filebrowser {
1756
+ display: flex;
1757
+ height: 100%;
1758
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
1759
+ font-size: 14px;
1760
+ color: #3D3229;
1761
+ border: 1px solid #DDD6CA;
1762
+ border-radius: 4px;
1763
+ overflow: hidden;
1764
+ background: #FAFAF8;
1827
1765
  }
1828
- .pict-content-loading {
1766
+
1767
+ .pict-filebrowser-browse-pane {
1768
+ width: 240px;
1769
+ min-width: 180px;
1770
+ border-right: 1px solid #DDD6CA;
1771
+ overflow-y: auto;
1772
+ background: #F5F0E8;
1773
+ flex-shrink: 0;
1774
+ }
1775
+
1776
+ .pict-filebrowser-main-pane {
1829
1777
  display: flex;
1830
- align-items: center;
1831
- justify-content: center;
1832
- min-height: 200px;
1833
- color: #8A7F72;
1834
- font-size: 1em;
1778
+ flex-direction: column;
1779
+ flex: 1;
1780
+ min-width: 0;
1835
1781
  }
1836
- .pict-content h1 {
1837
- font-size: 2em;
1838
- color: #3D3229;
1839
- border-bottom: 1px solid #DDD6CA;
1840
- padding-bottom: 0.3em;
1841
- margin-top: 0;
1782
+
1783
+ .pict-filebrowser-list-pane {
1784
+ flex: 1;
1785
+ overflow-y: auto;
1786
+ overflow-x: hidden;
1842
1787
  }
1843
- .pict-content h2 {
1844
- font-size: 1.5em;
1845
- color: #3D3229;
1846
- border-bottom: 1px solid #EAE3D8;
1847
- padding-bottom: 0.25em;
1848
- margin-top: 1.5em;
1788
+
1789
+ .pict-filebrowser-view-pane {
1790
+ border-top: 1px solid #DDD6CA;
1791
+ overflow-y: auto;
1792
+ background: #FAFAF8;
1849
1793
  }
1850
- .pict-content h3 {
1851
- font-size: 1.25em;
1852
- color: #3D3229;
1853
- margin-top: 1.25em;
1794
+
1795
+ /* --- Browsing: Tree --- */
1796
+ .pict-fb-tree {
1797
+ padding: 8px 0;
1854
1798
  }
1855
- .pict-content h4, .pict-content h5, .pict-content h6 {
1856
- color: #5E5549;
1857
- margin-top: 1em;
1799
+ .pict-fb-tree-node {
1800
+ display: flex;
1801
+ align-items: center;
1802
+ padding: 4px 8px 4px 0;
1803
+ cursor: pointer;
1804
+ user-select: none;
1805
+ white-space: nowrap;
1858
1806
  }
1859
- .pict-content p {
1860
- line-height: 1.7;
1861
- color: #423D37;
1862
- margin: 0.75em 0;
1807
+ .pict-fb-tree-node:hover {
1808
+ background: #EAE3D8;
1863
1809
  }
1864
- .pict-content a {
1865
- color: #2E7D74;
1866
- text-decoration: none;
1810
+ .pict-fb-tree-node.selected {
1811
+ background: #DDD6CA;
1812
+ font-weight: 600;
1867
1813
  }
1868
- .pict-content a:hover {
1869
- text-decoration: underline;
1814
+ .pict-fb-tree-toggle {
1815
+ display: inline-block;
1816
+ width: 16px;
1817
+ text-align: center;
1818
+ flex-shrink: 0;
1819
+ color: #8A7F72;
1820
+ font-size: 10px;
1870
1821
  }
1871
- .pict-content-code-wrap {
1872
- position: relative;
1873
- font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
1822
+ .pict-fb-tree-icon {
1823
+ margin-right: 6px;
1824
+ flex-shrink: 0;
1874
1825
  font-size: 14px;
1875
- line-height: 1.5;
1876
- border-radius: 6px;
1877
- overflow: auto;
1878
- margin: 1em 0;
1879
- background: #3D3229;
1880
1826
  }
1881
- .pict-content-code-wrap .pict-content-code-line-numbers {
1882
- position: absolute;
1883
- top: 0;
1884
- left: 0;
1885
- width: 40px;
1886
- padding: 1.25em 0;
1887
- text-align: right;
1888
- background: #342A22;
1889
- border-right: 1px solid #4A3F35;
1890
- color: #8A7F72;
1891
- font-size: 13px;
1892
- line-height: 1.5;
1893
- user-select: none;
1894
- pointer-events: none;
1895
- box-sizing: border-box;
1827
+ .pict-fb-tree-label {
1828
+ overflow: hidden;
1829
+ text-overflow: ellipsis;
1896
1830
  }
1897
- .pict-content-code-wrap .pict-content-code-line-numbers span {
1831
+ .pict-fb-tree-children {
1832
+ display: none;
1833
+ }
1834
+ .pict-fb-tree-children.expanded {
1898
1835
  display: block;
1899
- padding: 0 8px 0 0;
1900
1836
  }
1901
- .pict-content-code-wrap pre {
1902
- margin: 0;
1903
- background: #3D3229;
1904
- color: #E8E0D4;
1905
- padding: 1.25em 1.25em 1.25em 52px;
1906
- border-radius: 6px;
1907
- overflow-x: auto;
1908
- line-height: 1.5;
1909
- font-size: inherit;
1837
+
1838
+ /* --- Browsing: Search --- */
1839
+ .pict-fb-search {
1840
+ padding: 8px;
1910
1841
  }
1911
- .pict-content-code-wrap pre code {
1912
- background: none;
1913
- padding: 0;
1914
- color: inherit;
1915
- font-size: inherit;
1916
- font-family: inherit;
1917
- }
1918
- .pict-content-code-wrap .keyword { color: #C678DD; }
1919
- .pict-content-code-wrap .string { color: #98C379; }
1920
- .pict-content-code-wrap .number { color: #D19A66; }
1921
- .pict-content-code-wrap .comment { color: #7F848E; font-style: italic; }
1922
- .pict-content-code-wrap .operator { color: #56B6C2; }
1923
- .pict-content-code-wrap .punctuation { color: #E8E0D4; }
1924
- .pict-content-code-wrap .function-name { color: #61AFEF; }
1925
- .pict-content-code-wrap .property { color: #E06C75; }
1926
- .pict-content-code-wrap .tag { color: #E06C75; }
1927
- .pict-content-code-wrap .attr-name { color: #D19A66; }
1928
- .pict-content-code-wrap .attr-value { color: #98C379; }
1929
- .pict-content pre {
1930
- background: #3D3229;
1931
- color: #E8E0D4;
1932
- padding: 1.25em;
1933
- border-radius: 6px;
1934
- overflow-x: auto;
1935
- line-height: 1.5;
1936
- font-size: 0.9em;
1937
- }
1938
- .pict-content code {
1939
- background: #F0ECE4;
1940
- padding: 0.15em 0.4em;
1941
- border-radius: 3px;
1942
- font-size: 0.9em;
1943
- color: #9E6B47;
1944
- }
1945
- .pict-content pre code {
1946
- background: none;
1947
- padding: 0;
1948
- color: inherit;
1949
- font-size: inherit;
1950
- }
1951
- .pict-content blockquote {
1952
- border-left: 4px solid #2E7D74;
1953
- margin: 1em 0;
1954
- padding: 0.5em 1em;
1955
- background: #F7F5F0;
1956
- color: #5E5549;
1957
- }
1958
- .pict-content blockquote p {
1959
- margin: 0.25em 0;
1960
- }
1961
- .pict-content ul, .pict-content ol {
1962
- padding-left: 2em;
1963
- line-height: 1.8;
1964
- }
1965
- .pict-content li {
1966
- margin: 0.25em 0;
1967
- color: #423D37;
1968
- }
1969
- .pict-content hr {
1970
- border: none;
1971
- border-top: 1px solid #DDD6CA;
1972
- margin: 2em 0;
1973
- }
1974
- .pict-content table {
1975
- width: 100%;
1976
- border-collapse: collapse;
1977
- margin: 1em 0;
1978
- }
1979
- .pict-content table th {
1980
- background: #F5F0E8;
1981
- border: 1px solid #DDD6CA;
1982
- padding: 0.6em 0.8em;
1983
- text-align: left;
1984
- font-weight: 600;
1985
- color: #3D3229;
1986
- }
1987
- .pict-content table td {
1988
- border: 1px solid #DDD6CA;
1989
- padding: 0.5em 0.8em;
1990
- color: #423D37;
1991
- }
1992
- .pict-content table tr:nth-child(even) {
1993
- background: #F7F5F0;
1994
- }
1995
- .pict-content img {
1996
- max-width: 100%;
1997
- height: auto;
1998
- }
1999
- .pict-content pre.mermaid {
2000
- background: #fff;
2001
- color: #3D3229;
2002
- text-align: center;
2003
- padding: 1em;
2004
- }
2005
- .pict-content .pict-content-katex-display {
2006
- text-align: center;
2007
- margin: 1em 0;
2008
- padding: 0.5em;
2009
- overflow-x: auto;
2010
- }
2011
- .pict-content .pict-content-katex-inline {
2012
- display: inline;
2013
- }
2014
- `,Templates:[{Hash:"Pict-Content-Template",Template:/*html*/`
2015
- <div class="pict-content" id="Pict-Content-Body">
2016
- <div class="pict-content-loading">Loading content...</div>
2017
- </div>
2018
- `}],Renderables:[{RenderableHash:"Pict-Content-Display",TemplateHash:"Pict-Content-Template",DestinationAddress:"#Pict-Content-Container",RenderMethod:"replace"}]};class PictContentView extends libPictView{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);}/**
2019
- * Display parsed HTML content in the content area.
2020
- *
2021
- * @param {string} pHTMLContent - The HTML to display
2022
- * @param {string} [pContainerID] - The container element ID (defaults to 'Pict-Content-Body')
2023
- */displayContent(pHTMLContent,pContainerID){let tmpContainerID=pContainerID||'Pict-Content-Body';this.pict.ContentAssignment.assignContent('#'+tmpContainerID,pHTMLContent);// Scroll to top of content area
2024
- let tmpContentContainer=document.getElementById(tmpContainerID);if(tmpContentContainer&&tmpContentContainer.parentElement){tmpContentContainer.parentElement.scrollTop=0;}// Post-render: initialize Mermaid diagrams if mermaid is available
2025
- this.renderMermaidDiagrams(tmpContainerID);// Post-render: render KaTeX equations if katex is available
2026
- this.renderKaTeXEquations(tmpContainerID);}/**
2027
- * Render any Mermaid diagram blocks in the content area.
2028
- * Mermaid blocks are `<pre class="mermaid">` elements produced by parseMarkdown.
2029
- *
2030
- * @param {string} [pContainerID] - The container element ID (defaults to 'Pict-Content-Body')
2031
- */renderMermaidDiagrams(pContainerID){if(typeof mermaid==='undefined'){return;}let tmpContainerID=pContainerID||'Pict-Content-Body';let tmpContentBody=document.getElementById(tmpContainerID);if(!tmpContentBody){return;}let tmpMermaidElements=tmpContentBody.querySelectorAll('pre.mermaid');if(tmpMermaidElements.length<1){return;}// mermaid.run() will process all pre.mermaid elements in the container
2032
- try{mermaid.run({nodes:tmpMermaidElements});}catch(pError){this.log.error('Mermaid rendering error: '+pError.message);}}/**
2033
- * Render KaTeX inline and display math elements in the content area.
2034
- * Inline: `<span class="pict-content-katex-inline">`
2035
- * Display: `<div class="pict-content-katex-display">`
2036
- *
2037
- * @param {string} [pContainerID] - The container element ID (defaults to 'Pict-Content-Body')
2038
- */renderKaTeXEquations(pContainerID){if(typeof katex==='undefined'){return;}let tmpContainerID=pContainerID||'Pict-Content-Body';let tmpContentBody=document.getElementById(tmpContainerID);if(!tmpContentBody){return;}// Render inline math
2039
- let tmpInlineElements=tmpContentBody.querySelectorAll('.pict-content-katex-inline');for(let i=0;i<tmpInlineElements.length;i++){try{katex.render(tmpInlineElements[i].textContent,tmpInlineElements[i],{throwOnError:false,displayMode:false});}catch(pError){this.log.warn('KaTeX inline error: '+pError.message);}}// Render display math
2040
- let tmpDisplayElements=tmpContentBody.querySelectorAll('.pict-content-katex-display');for(let i=0;i<tmpDisplayElements.length;i++){try{katex.render(tmpDisplayElements[i].textContent,tmpDisplayElements[i],{throwOnError:false,displayMode:true});}catch(pError){this.log.warn('KaTeX display error: '+pError.message);}}}/**
2041
- * Show a loading indicator.
2042
- *
2043
- * @param {string} [pMessage] - Loading message (defaults to 'Loading content...')
2044
- * @param {string} [pContainerID] - The container element ID (defaults to 'Pict-Content-Body')
2045
- */showLoading(pMessage,pContainerID){let tmpContainerID=pContainerID||'Pict-Content-Body';let tmpMessage=pMessage||'Loading content...';this.pict.ContentAssignment.assignContent('#'+tmpContainerID,'<div class="pict-content-loading">'+tmpMessage+'</div>');}}module.exports=PictContentView;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],53:[function(require,module,exports){module.exports={"ViewIdentifier":"Pict-FileBrowser","DefaultRenderable":"FileBrowser-Container","DefaultDestinationAddress":"#Pict-FileBrowser-Container","AutoRender":false,// --- FileBrowser State ---
2046
- // These are the four core state values for the file browser.
2047
- // They live in AppData at the addresses below.
2048
- "StateAddresses":{"Layout":"AppData.PictFileBrowser.Layout","RootLocation":"AppData.PictFileBrowser.RootLocation","CurrentLocation":"AppData.PictFileBrowser.CurrentLocation","CurrentFile":"AppData.PictFileBrowser.CurrentFile"},// Default state values
2049
- "DefaultState":{"Layout":"browser-detail","RootLocation":"/","CurrentLocation":"","CurrentFile":null},// --- File Data ---
2050
- // The consuming application must populate this address with file/folder data.
2051
- // Expected format: Array of objects with at minimum { Name, Type }
2052
- // Type: "folder" or "file"
2053
- // Additional properties: Size, Modified, Extension, MimeType, Path, Icon, ThumbnailURL
2054
- "FileListAddress":"AppData.PictFileBrowser.FileList","FolderTreeAddress":"AppData.PictFileBrowser.FolderTree","ChildFolderCacheAddress":"AppData.PictFileBrowser.ChildFolderCache",// --- Templates ---
2055
- "Templates":[{"Hash":"FileBrowser-Container-Template","Template":/*html*/`
2056
- <div class="pict-filebrowser" id="Pict-FileBrowser-Wrap">
2057
- <div class="pict-filebrowser-browse-pane" id="Pict-FileBrowser-BrowsePane"></div>
2058
- <div class="pict-filebrowser-main-pane">
2059
- <div class="pict-filebrowser-list-pane" id="Pict-FileBrowser-ListPane"></div>
2060
- <div class="pict-filebrowser-view-pane" id="Pict-FileBrowser-ViewPane"></div>
2061
- </div>
2062
- </div>
2063
- `}],"Renderables":[{"RenderableHash":"FileBrowser-Container","TemplateHash":"FileBrowser-Container-Template","DestinationAddress":"#Pict-FileBrowser-Container","RenderMethod":"replace"}],// --- CSS ---
2064
- "CSS":/*css*/`
2065
- .pict-filebrowser {
2066
- display: flex;
2067
- height: 100%;
2068
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
2069
- font-size: 14px;
2070
- color: #3D3229;
2071
- border: 1px solid #DDD6CA;
2072
- border-radius: 4px;
2073
- overflow: hidden;
2074
- background: #FAFAF8;
2075
- }
2076
-
2077
- .pict-filebrowser-browse-pane {
2078
- width: 240px;
2079
- min-width: 180px;
2080
- border-right: 1px solid #DDD6CA;
2081
- overflow-y: auto;
2082
- background: #F5F0E8;
2083
- flex-shrink: 0;
2084
- }
2085
-
2086
- .pict-filebrowser-main-pane {
2087
- display: flex;
2088
- flex-direction: column;
2089
- flex: 1;
2090
- min-width: 0;
2091
- }
2092
-
2093
- .pict-filebrowser-list-pane {
2094
- flex: 1;
2095
- overflow-y: auto;
2096
- overflow-x: hidden;
2097
- }
2098
-
2099
- .pict-filebrowser-view-pane {
2100
- border-top: 1px solid #DDD6CA;
2101
- overflow-y: auto;
2102
- background: #FAFAF8;
2103
- }
2104
-
2105
- /* --- Browsing: Tree --- */
2106
- .pict-fb-tree {
2107
- padding: 8px 0;
2108
- }
2109
- .pict-fb-tree-node {
2110
- display: flex;
2111
- align-items: center;
2112
- padding: 4px 8px 4px 0;
2113
- cursor: pointer;
2114
- user-select: none;
2115
- white-space: nowrap;
2116
- }
2117
- .pict-fb-tree-node:hover {
2118
- background: #EAE3D8;
2119
- }
2120
- .pict-fb-tree-node.selected {
2121
- background: #DDD6CA;
2122
- font-weight: 600;
2123
- }
2124
- .pict-fb-tree-toggle {
2125
- display: inline-block;
2126
- width: 16px;
2127
- text-align: center;
2128
- flex-shrink: 0;
2129
- color: #8A7F72;
2130
- font-size: 10px;
2131
- }
2132
- .pict-fb-tree-icon {
2133
- margin-right: 6px;
2134
- flex-shrink: 0;
2135
- font-size: 14px;
2136
- }
2137
- .pict-fb-tree-label {
2138
- overflow: hidden;
2139
- text-overflow: ellipsis;
2140
- }
2141
- .pict-fb-tree-children {
2142
- display: none;
2143
- }
2144
- .pict-fb-tree-children.expanded {
2145
- display: block;
2146
- }
2147
-
2148
- /* --- Browsing: Search --- */
2149
- .pict-fb-search {
2150
- padding: 8px;
2151
- }
2152
- .pict-fb-search-input {
2153
- width: 100%;
2154
- box-sizing: border-box;
2155
- padding: 6px 10px;
2156
- border: 1px solid #DDD6CA;
2157
- border-radius: 4px;
2158
- font-size: 13px;
2159
- outline: none;
2160
- background: #fff;
1842
+ .pict-fb-search-input {
1843
+ width: 100%;
1844
+ box-sizing: border-box;
1845
+ padding: 6px 10px;
1846
+ border: 1px solid #DDD6CA;
1847
+ border-radius: 4px;
1848
+ font-size: 13px;
1849
+ outline: none;
1850
+ background: #fff;
2161
1851
  }
2162
1852
  .pict-fb-search-input:focus {
2163
1853
  border-color: #2E7D74;
@@ -2394,7 +2084,7 @@ let tmpDisplayElements=tmpContentBody.querySelectorAll('.pict-content-katex-disp
2394
2084
  color: #8A7F72;
2395
2085
  font-style: italic;
2396
2086
  }
2397
- `};},{}],54:[function(require,module,exports){// Pict Section FileBrowser
2087
+ `};},{}],51:[function(require,module,exports){// Pict Section FileBrowser
2398
2088
  // A composable file browser section with browsing, listing, and viewing views.
2399
2089
  // The main container view
2400
2090
  module.exports=require('./views/Pict-View-FileBrowser.js');// --- Providers (base classes for each view type) ---
@@ -2402,7 +2092,7 @@ module.exports.PictFileBrowserBrowseProvider=require('./providers/Pict-Provider-
2402
2092
  module.exports.PictViewBrowseTree=require('./views/Pict-View-FileBrowser-BrowseTree.js');module.exports.PictViewBrowseSearch=require('./views/Pict-View-FileBrowser-BrowseSearch.js');// --- Listing Views ---
2403
2093
  module.exports.PictViewListDetail=require('./views/Pict-View-FileBrowser-ListDetail.js');module.exports.PictViewListIcons=require('./views/Pict-View-FileBrowser-ListIcons.js');// --- Viewing Views ---
2404
2094
  module.exports.PictViewFileInfo=require('./views/Pict-View-FileBrowser-ViewFileInfo.js');module.exports.PictViewImageViewer=require('./views/Pict-View-FileBrowser-ViewImage.js');// --- Service (Fable service with REST endpoints + static web app) ---
2405
- module.exports.FileBrowserService=require('./services/Pict-Service-FileBrowser.js');},{"./providers/Pict-Provider-FileBrowserBrowse.js":55,"./providers/Pict-Provider-FileBrowserIcons.js":56,"./providers/Pict-Provider-FileBrowserLayout.js":57,"./providers/Pict-Provider-FileBrowserList.js":58,"./providers/Pict-Provider-FileBrowserView.js":59,"./services/Pict-Service-FileBrowser.js":60,"./views/Pict-View-FileBrowser-BrowseSearch.js":61,"./views/Pict-View-FileBrowser-BrowseTree.js":62,"./views/Pict-View-FileBrowser-ListDetail.js":63,"./views/Pict-View-FileBrowser-ListIcons.js":64,"./views/Pict-View-FileBrowser-ViewFileInfo.js":65,"./views/Pict-View-FileBrowser-ViewImage.js":66,"./views/Pict-View-FileBrowser.js":67}],55:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-Browse","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};/**
2095
+ module.exports.FileBrowserService=require('./services/Pict-Service-FileBrowser.js');},{"./providers/Pict-Provider-FileBrowserBrowse.js":52,"./providers/Pict-Provider-FileBrowserIcons.js":53,"./providers/Pict-Provider-FileBrowserLayout.js":54,"./providers/Pict-Provider-FileBrowserList.js":55,"./providers/Pict-Provider-FileBrowserView.js":56,"./services/Pict-Service-FileBrowser.js":57,"./views/Pict-View-FileBrowser-BrowseSearch.js":58,"./views/Pict-View-FileBrowser-BrowseTree.js":59,"./views/Pict-View-FileBrowser-ListDetail.js":60,"./views/Pict-View-FileBrowser-ListIcons.js":61,"./views/Pict-View-FileBrowser-ViewFileInfo.js":62,"./views/Pict-View-FileBrowser-ViewImage.js":63,"./views/Pict-View-FileBrowser.js":64}],52:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-Browse","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};/**
2406
2096
  * Base provider for browsing-type views (tree navigation, search).
2407
2097
  *
2408
2098
  * Subclass or override to customize how folder structures are resolved,
@@ -2457,7 +2147,7 @@ this.pict.manifest.setValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmp
2457
2147
  * @param {string} pKey - The option key
2458
2148
  * @param {*} pDefault - Default value
2459
2149
  * @returns {*} The option value
2460
- */getFileBrowserOption(pKey,pDefault){if(this.pict.views&&this.pict.views['Pict-FileBrowser']){let tmpOptions=this.pict.views['Pict-FileBrowser'].options;if(tmpOptions&&pKey in tmpOptions){return tmpOptions[pKey];}}return pDefault;}}module.exports=PictFileBrowserBrowseProvider;module.exports.default_configuration=_DefaultProviderConfiguration;},{"pict-provider":46}],56:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-Icons","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};// ---- Color palette (matches filebrowser CSS) ----
2150
+ */getFileBrowserOption(pKey,pDefault){if(this.pict.views&&this.pict.views['Pict-FileBrowser']){let tmpOptions=this.pict.views['Pict-FileBrowser'].options;if(tmpOptions&&pKey in tmpOptions){return tmpOptions[pKey];}}return pDefault;}}module.exports=PictFileBrowserBrowseProvider;module.exports.default_configuration=_DefaultProviderConfiguration;},{"pict-provider":46}],53:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-Icons","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};// ---- Color palette (matches filebrowser CSS) ----
2461
2151
  const _Colors={Primary:'#3D3229',Accent:'#2E7D74',Muted:'#8A7F72',Light:'#F5F0E8',WarmBeige:'#EAE3D8',TealTint:'#E0EDE9',Lavender:'#E8E0F0',AmberTint:'#F0E8D0'};// ====================================================================
2462
2152
  // RETRO / HAND-DRAWN SVG ICON SET
2463
2153
  //
@@ -2550,7 +2240,7 @@ return this.getIcon('file',tmpSize);}/**
2550
2240
  */getExtensionMap(){return Object.assign({},this._extensionMap);}/**
2551
2241
  * Inject CSS classes for icon sizing into the pict CSSMap.
2552
2242
  * Called automatically by views that use icons.
2553
- */injectCSS(){if(this._cssInjected){return;}if(this.pict&&this.pict.CSSMap){this.pict.CSSMap.addCSS('PictFileBrowserIcons','.pict-fb-svg-icon { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; }\n'+'.pict-fb-svg-icon svg { display: block; }\n'+'.pict-fb-tree-icon svg { width: 16px; height: 16px; }\n'+'.pict-fb-detail-icon svg { width: 16px; height: 16px; }\n'+'.pict-fb-icon-graphic svg { width: 36px; height: 36px; }\n'+'.pict-fb-tree-toggle svg { width: 10px; height: 10px; }\n');this._cssInjected=true;}}}module.exports=PictFileBrowserIconProvider;module.exports.default_configuration=_DefaultProviderConfiguration;module.exports.BuiltInIcons=_BuiltInIcons;module.exports.ExtensionMap=_ExtensionMap;module.exports.Colors=_Colors;},{"pict-provider":46}],57:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-Layout","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};/**
2243
+ */injectCSS(){if(this._cssInjected){return;}if(this.pict&&this.pict.CSSMap){this.pict.CSSMap.addCSS('PictFileBrowserIcons','.pict-fb-svg-icon { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; }\n'+'.pict-fb-svg-icon svg { display: block; }\n'+'.pict-fb-tree-icon svg { width: 16px; height: 16px; }\n'+'.pict-fb-detail-icon svg { width: 16px; height: 16px; }\n'+'.pict-fb-icon-graphic svg { width: 36px; height: 36px; }\n'+'.pict-fb-tree-toggle svg { width: 10px; height: 10px; }\n');this._cssInjected=true;}}}module.exports=PictFileBrowserIconProvider;module.exports.default_configuration=_DefaultProviderConfiguration;module.exports.BuiltInIcons=_BuiltInIcons;module.exports.ExtensionMap=_ExtensionMap;module.exports.Colors=_Colors;},{"pict-provider":46}],54:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-Layout","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};/**
2554
2244
  * Layout definitions.
2555
2245
  *
2556
2246
  * Each layout specifies:
@@ -2612,7 +2302,7 @@ this.layouts=JSON.parse(JSON.stringify(_BuiltInLayouts));}/**
2612
2302
  * @param {string} pKey - The option key
2613
2303
  * @param {*} pDefault - Default value
2614
2304
  * @returns {*} The option value
2615
- */getFileBrowserOption(pKey,pDefault){if(this.pict.views&&this.pict.views['Pict-FileBrowser']){let tmpOptions=this.pict.views['Pict-FileBrowser'].options;if(tmpOptions&&pKey in tmpOptions){return tmpOptions[pKey];}}return pDefault;}}module.exports=PictFileBrowserLayoutProvider;module.exports.default_configuration=_DefaultProviderConfiguration;module.exports.BuiltInLayouts=_BuiltInLayouts;},{"pict-provider":46}],58:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-List","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};/**
2305
+ */getFileBrowserOption(pKey,pDefault){if(this.pict.views&&this.pict.views['Pict-FileBrowser']){let tmpOptions=this.pict.views['Pict-FileBrowser'].options;if(tmpOptions&&pKey in tmpOptions){return tmpOptions[pKey];}}return pDefault;}}module.exports=PictFileBrowserLayoutProvider;module.exports.default_configuration=_DefaultProviderConfiguration;module.exports.BuiltInLayouts=_BuiltInLayouts;},{"pict-provider":46}],55:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-List","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};/**
2616
2306
  * Base provider for listing-type views (detail list, icon grid).
2617
2307
  *
2618
2308
  * Handles file list retrieval, sorting, filtering, and selection.
@@ -2671,7 +2361,7 @@ if(pEntry.Type==='folder'){return'\uD83D\uDCC1';}let tmpExt=(pEntry.Extension||'
2671
2361
  * @param {string} pKey - The option key
2672
2362
  * @param {*} pDefault - Default value
2673
2363
  * @returns {*} The option value
2674
- */getFileBrowserOption(pKey,pDefault){if(this.pict.views&&this.pict.views['Pict-FileBrowser']){let tmpOptions=this.pict.views['Pict-FileBrowser'].options;if(tmpOptions&&pKey in tmpOptions){return tmpOptions[pKey];}}return pDefault;}}module.exports=PictFileBrowserListProvider;module.exports.default_configuration=_DefaultProviderConfiguration;},{"pict-provider":46}],59:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-View","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};/**
2364
+ */getFileBrowserOption(pKey,pDefault){if(this.pict.views&&this.pict.views['Pict-FileBrowser']){let tmpOptions=this.pict.views['Pict-FileBrowser'].options;if(tmpOptions&&pKey in tmpOptions){return tmpOptions[pKey];}}return pDefault;}}module.exports=PictFileBrowserListProvider;module.exports.default_configuration=_DefaultProviderConfiguration;},{"pict-provider":46}],56:[function(require,module,exports){const libPictProvider=require('pict-provider');const _DefaultProviderConfiguration={"ProviderIdentifier":"Pict-FileBrowser-View","AutoInitialize":true,"AutoInitializeOrdinal":0,"AutoSolveWithApp":true,"AutoSolveOrdinal":0};/**
2675
2365
  * Base provider for viewing-type views (file info, image preview).
2676
2366
  *
2677
2367
  * Handles retrieval of the currently selected file's metadata and
@@ -2707,7 +2397,7 @@ let tmpExt=(tmpEntry.Extension||'').toLowerCase();let tmpImageExtensions=['.jpg'
2707
2397
  * @param {string} pKey - The option key
2708
2398
  * @param {*} pDefault - Default value
2709
2399
  * @returns {*} The option value
2710
- */getFileBrowserOption(pKey,pDefault){if(this.pict.views&&this.pict.views['Pict-FileBrowser']){let tmpOptions=this.pict.views['Pict-FileBrowser'].options;if(tmpOptions&&pKey in tmpOptions){return tmpOptions[pKey];}}return pDefault;}}module.exports=PictFileBrowserViewProvider;module.exports.default_configuration=_DefaultProviderConfiguration;},{"pict-provider":46}],60:[function(require,module,exports){(function(__dirname){(function(){/**
2400
+ */getFileBrowserOption(pKey,pDefault){if(this.pict.views&&this.pict.views['Pict-FileBrowser']){let tmpOptions=this.pict.views['Pict-FileBrowser'].options;if(tmpOptions&&pKey in tmpOptions){return tmpOptions[pKey];}}return pDefault;}}module.exports=PictFileBrowserViewProvider;module.exports.default_configuration=_DefaultProviderConfiguration;},{"pict-provider":46}],57:[function(require,module,exports){(function(__dirname){(function(){/**
2711
2401
  * Pict FileBrowser Service
2712
2402
  *
2713
2403
  * A Fable service that provides:
@@ -2818,7 +2508,7 @@ tmpNodes.sort((pA,pB)=>{return pA.Name.localeCompare(pB.Name);});return fCallbac
2818
2508
  *
2819
2509
  * @param {string} pRelativePath - Path relative to basePath
2820
2510
  * @param {Function} fCallback - Callback(pError, pInfo)
2821
- */getFileInfo(pRelativePath,fCallback){let tmpAbsolutePath=this.resolveSafePath(pRelativePath);if(!tmpAbsolutePath){return fCallback(new Error('Invalid path'));}libFS.stat(tmpAbsolutePath,(pError,pStats)=>{if(pError){if(pError.code==='ENOENT'){return fCallback(new Error('Path not found'));}return fCallback(pError);}let tmpName=libPath.basename(tmpAbsolutePath);let tmpInfo={Name:tmpName,Path:pRelativePath||tmpName,Type:pStats.isDirectory()?'folder':'file',Size:pStats.size,Modified:pStats.mtime,Created:pStats.birthtime};if(!pStats.isDirectory()){tmpInfo.Extension=libPath.extname(tmpName);}return fCallback(null,tmpInfo);});}}module.exports=PictFileBrowserService;module.exports.default_configuration=_DefaultServiceConfiguration;}).call(this);}).call(this,"/node_modules/pict-section-filebrowser/source/services");},{"fable-serviceproviderbase":20,"fs":2,"path":42,"url":113}],61:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-BrowseSearch","DefaultRenderable":"BrowseSearch-Container","DefaultDestinationAddress":"#Pict-FileBrowser-BrowsePane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-BrowseSearch-Container-Template","Template":/*html*/`
2511
+ */getFileInfo(pRelativePath,fCallback){let tmpAbsolutePath=this.resolveSafePath(pRelativePath);if(!tmpAbsolutePath){return fCallback(new Error('Invalid path'));}libFS.stat(tmpAbsolutePath,(pError,pStats)=>{if(pError){if(pError.code==='ENOENT'){return fCallback(new Error('Path not found'));}return fCallback(pError);}let tmpName=libPath.basename(tmpAbsolutePath);let tmpInfo={Name:tmpName,Path:pRelativePath||tmpName,Type:pStats.isDirectory()?'folder':'file',Size:pStats.size,Modified:pStats.mtime,Created:pStats.birthtime};if(!pStats.isDirectory()){tmpInfo.Extension=libPath.extname(tmpName);}return fCallback(null,tmpInfo);});}}module.exports=PictFileBrowserService;module.exports.default_configuration=_DefaultServiceConfiguration;}).call(this);}).call(this,"/node_modules/pict-section-filebrowser/source/services");},{"fable-serviceproviderbase":20,"fs":2,"path":42,"url":113}],58:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-BrowseSearch","DefaultRenderable":"BrowseSearch-Container","DefaultDestinationAddress":"#Pict-FileBrowser-BrowsePane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-BrowseSearch-Container-Template","Template":/*html*/`
2822
2512
  <div class="pict-fb-search">
2823
2513
  <input type="text" class="pict-fb-search-input"
2824
2514
  id="Pict-FileBrowser-SearchInput"
@@ -2853,7 +2543,7 @@ this._lastResults=tmpResults;}/**
2853
2543
  * Handle clicking a search result.
2854
2544
  *
2855
2545
  * @param {number} pIndex - The result index
2856
- */selectResult(pIndex){if(!this._lastResults||pIndex>=this._lastResults.length){return;}let tmpEntry=this._lastResults[pIndex];let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(tmpListProvider){tmpListProvider.openEntry(tmpEntry);}}}module.exports=PictViewFileBrowserBrowseSearch;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],62:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-BrowseTree","DefaultRenderable":"BrowseTree-Container","DefaultDestinationAddress":"#Pict-FileBrowser-BrowsePane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-BrowseTree-Container-Template","Template":/*html*/`<div class="pict-fb-tree" id="Pict-FileBrowser-Tree"></div>`},{"Hash":"FileBrowser-BrowseTree-Node-Template","Template":/*html*/`
2546
+ */selectResult(pIndex){if(!this._lastResults||pIndex>=this._lastResults.length){return;}let tmpEntry=this._lastResults[pIndex];let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(tmpListProvider){tmpListProvider.openEntry(tmpEntry);}}}module.exports=PictViewFileBrowserBrowseSearch;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],59:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-BrowseTree","DefaultRenderable":"BrowseTree-Container","DefaultDestinationAddress":"#Pict-FileBrowser-BrowsePane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-BrowseTree-Container-Template","Template":/*html*/`<div class="pict-fb-tree" id="Pict-FileBrowser-Tree"></div>`},{"Hash":"FileBrowser-BrowseTree-Node-Template","Template":/*html*/`
2857
2547
  <div class="pict-fb-tree-node{~D:Record.SelectedClass~}" style="padding-left: {~D:Record.Indent~}px;" data-path="{~D:Record.Path~}" onclick="{~D:Record.ClickHandler~}">
2858
2548
  <span class="{~D:Record.ToggleClass~}" onclick="{~D:Record.ToggleHandler~}">{~D:Record.ToggleIcon~}</span>
2859
2549
  <span class="pict-fb-tree-icon">{~D:Record.Icon~}</span>
@@ -2910,7 +2600,7 @@ let tmpCached=tmpBrowseProvider.getChildFolders(pPath);if(!tmpCached){if(typeof
2910
2600
  * Get the current location from state.
2911
2601
  *
2912
2602
  * @returns {string} The current location path
2913
- */getCurrentLocation(){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses.CurrentLocation||'AppData.PictFileBrowser.CurrentLocation';return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress)||'';}}module.exports=PictViewFileBrowserBrowseTree;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],63:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ListDetail","DefaultRenderable":"ListDetail-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ListPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ListDetail-Container-Template","Template":/*html*/`
2603
+ */getCurrentLocation(){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses.CurrentLocation||'AppData.PictFileBrowser.CurrentLocation';return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress)||'';}}module.exports=PictViewFileBrowserBrowseTree;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],60:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ListDetail","DefaultRenderable":"ListDetail-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ListPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ListDetail-Container-Template","Template":/*html*/`
2914
2604
  <div class="pict-fb-detail" id="Pict-FileBrowser-DetailList">
2915
2605
  <div class="pict-fb-breadcrumb" id="Pict-FileBrowser-Breadcrumb"></div>
2916
2606
  <div class="pict-fb-detail-header">
@@ -2962,7 +2652,7 @@ tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Current-Template'
2962
2652
  * Get the current location from state.
2963
2653
  *
2964
2654
  * @returns {string} The current location path
2965
- */getCurrentLocation(){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses.CurrentLocation||'AppData.PictFileBrowser.CurrentLocation';return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress)||'';}}module.exports=PictViewFileBrowserListDetail;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],64:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ListIcons","DefaultRenderable":"ListIcons-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ListPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ListIcons-Container-Template","Template":/*html*/`
2655
+ */getCurrentLocation(){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses.CurrentLocation||'AppData.PictFileBrowser.CurrentLocation';return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress)||'';}}module.exports=PictViewFileBrowserListDetail;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],61:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ListIcons","DefaultRenderable":"ListIcons-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ListPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ListIcons-Container-Template","Template":/*html*/`
2966
2656
  <div id="Pict-FileBrowser-IconList">
2967
2657
  <div class="pict-fb-breadcrumb" id="Pict-FileBrowser-IconBreadcrumb"></div>
2968
2658
  <div class="pict-fb-icons" id="Pict-FileBrowser-IconGrid"></div>
@@ -2972,107 +2662,419 @@ tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Current-Template'
2972
2662
  <div class="pict-fb-icon-graphic">{~D:Record.Icon~}</div>
2973
2663
  <div class="pict-fb-icon-label">{~D:Record.Name~}</div>
2974
2664
  </div>
2975
- `},{"Hash":"FileBrowser-ListIcons-Empty-Template","Template":/*html*/`<div class="pict-fb-empty">{~D:Record.Message~}</div>`}],"Renderables":[{"RenderableHash":"ListIcons-Container","TemplateHash":"FileBrowser-ListIcons-Container-Template","DestinationAddress":"#Pict-FileBrowser-ListPane","RenderMethod":"replace"}]};/**
2976
- * Listing view that shows files as an icon grid.
2977
- *
2978
- * Each file or folder is displayed as a large icon with a label beneath it.
2979
- * Single-click selects, double-click opens folders or selects files.
2980
- */class PictViewFileBrowserListIcons extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_ViewConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);this._cachedFileList=[];}/**
2981
- * After rendering the container, populate the icon grid.
2982
- */onAfterRender(pRenderable){this.rebuildGrid();this.rebuildBreadcrumb();this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
2983
- * Rebuild the icon grid.
2984
- */rebuildGrid(){let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(!tmpListProvider){return;}let tmpFileList=tmpListProvider.getSortedFileList();this._cachedFileList=tmpFileList;let tmpSelectedFile=tmpListProvider.getSelectedFile();if(tmpFileList.length===0){let tmpEmptyHTML=this.pict.parseTemplateByHash('FileBrowser-ListIcons-Empty-Template',{Message:'This folder is empty'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-IconGrid',tmpEmptyHTML);return;}let tmpHTML='';for(let i=0;i<tmpFileList.length;i++){let tmpEntry=tmpFileList[i];let tmpIsSelected=tmpSelectedFile&&tmpSelectedFile.Name===tmpEntry.Name&&tmpSelectedFile.Path===tmpEntry.Path;let tmpRecord={Index:i,Name:tmpEntry.Name,Icon:tmpListProvider.getEntryIcon(tmpEntry),SelectedClass:tmpIsSelected?' selected':'',ClickHandler:"pict.views['"+this.Hash+"'].selectEntry("+i+")",DblClickHandler:"pict.views['"+this.Hash+"'].openEntry("+i+")"};tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-ListIcons-Item-Template',tmpRecord);}this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-IconGrid',tmpHTML);}/**
2985
- * Rebuild the breadcrumb navigation bar.
2986
- */rebuildBreadcrumb(){let tmpCurrentLocation=this.getCurrentLocation();let tmpHTML='';// Root segment
2987
- tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Segment-Template',{Label:'\uD83C\uDFE0',ClickHandler:"pict.views['"+this.Hash+"'].navigateToPath('')"});if(tmpCurrentLocation){let tmpParts=tmpCurrentLocation.split('/');let tmpAccumulatedPath='';for(let i=0;i<tmpParts.length;i++){tmpAccumulatedPath=tmpAccumulatedPath?tmpAccumulatedPath+'/'+tmpParts[i]:tmpParts[i];tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Separator-Template',{});if(i===tmpParts.length-1){tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Current-Template',{Label:tmpParts[i]});}else{let tmpPath=tmpAccumulatedPath;tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Segment-Template',{Label:tmpParts[i],ClickHandler:"pict.views['"+this.Hash+"'].navigateToPath('"+tmpPath+"')"});}}}this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-IconBreadcrumb',tmpHTML);}/**
2988
- * Select a file entry by index.
2989
- *
2990
- * @param {number} pIndex - The index in the cached file list
2991
- */selectEntry(pIndex){if(pIndex<0||pIndex>=this._cachedFileList.length){return;}let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(tmpListProvider){tmpListProvider.selectFile(this._cachedFileList[pIndex]);}this.rebuildGrid();}/**
2992
- * Open an entry (navigate into folder or select file).
2993
- *
2994
- * @param {number} pIndex - The index in the cached file list
2995
- */openEntry(pIndex){if(pIndex<0||pIndex>=this._cachedFileList.length){return;}let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(tmpListProvider){tmpListProvider.openEntry(this._cachedFileList[pIndex]);}this.rebuildGrid();this.rebuildBreadcrumb();}/**
2996
- * Navigate to a path via breadcrumb.
2665
+ `},{"Hash":"FileBrowser-ListIcons-Empty-Template","Template":/*html*/`<div class="pict-fb-empty">{~D:Record.Message~}</div>`}],"Renderables":[{"RenderableHash":"ListIcons-Container","TemplateHash":"FileBrowser-ListIcons-Container-Template","DestinationAddress":"#Pict-FileBrowser-ListPane","RenderMethod":"replace"}]};/**
2666
+ * Listing view that shows files as an icon grid.
2667
+ *
2668
+ * Each file or folder is displayed as a large icon with a label beneath it.
2669
+ * Single-click selects, double-click opens folders or selects files.
2670
+ */class PictViewFileBrowserListIcons extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_ViewConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);this._cachedFileList=[];}/**
2671
+ * After rendering the container, populate the icon grid.
2672
+ */onAfterRender(pRenderable){this.rebuildGrid();this.rebuildBreadcrumb();this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
2673
+ * Rebuild the icon grid.
2674
+ */rebuildGrid(){let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(!tmpListProvider){return;}let tmpFileList=tmpListProvider.getSortedFileList();this._cachedFileList=tmpFileList;let tmpSelectedFile=tmpListProvider.getSelectedFile();if(tmpFileList.length===0){let tmpEmptyHTML=this.pict.parseTemplateByHash('FileBrowser-ListIcons-Empty-Template',{Message:'This folder is empty'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-IconGrid',tmpEmptyHTML);return;}let tmpHTML='';for(let i=0;i<tmpFileList.length;i++){let tmpEntry=tmpFileList[i];let tmpIsSelected=tmpSelectedFile&&tmpSelectedFile.Name===tmpEntry.Name&&tmpSelectedFile.Path===tmpEntry.Path;let tmpRecord={Index:i,Name:tmpEntry.Name,Icon:tmpListProvider.getEntryIcon(tmpEntry),SelectedClass:tmpIsSelected?' selected':'',ClickHandler:"pict.views['"+this.Hash+"'].selectEntry("+i+")",DblClickHandler:"pict.views['"+this.Hash+"'].openEntry("+i+")"};tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-ListIcons-Item-Template',tmpRecord);}this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-IconGrid',tmpHTML);}/**
2675
+ * Rebuild the breadcrumb navigation bar.
2676
+ */rebuildBreadcrumb(){let tmpCurrentLocation=this.getCurrentLocation();let tmpHTML='';// Root segment
2677
+ tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Segment-Template',{Label:'\uD83C\uDFE0',ClickHandler:"pict.views['"+this.Hash+"'].navigateToPath('')"});if(tmpCurrentLocation){let tmpParts=tmpCurrentLocation.split('/');let tmpAccumulatedPath='';for(let i=0;i<tmpParts.length;i++){tmpAccumulatedPath=tmpAccumulatedPath?tmpAccumulatedPath+'/'+tmpParts[i]:tmpParts[i];tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Separator-Template',{});if(i===tmpParts.length-1){tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Current-Template',{Label:tmpParts[i]});}else{let tmpPath=tmpAccumulatedPath;tmpHTML+=this.pict.parseTemplateByHash('FileBrowser-Breadcrumb-Segment-Template',{Label:tmpParts[i],ClickHandler:"pict.views['"+this.Hash+"'].navigateToPath('"+tmpPath+"')"});}}}this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-IconBreadcrumb',tmpHTML);}/**
2678
+ * Select a file entry by index.
2679
+ *
2680
+ * @param {number} pIndex - The index in the cached file list
2681
+ */selectEntry(pIndex){if(pIndex<0||pIndex>=this._cachedFileList.length){return;}let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(tmpListProvider){tmpListProvider.selectFile(this._cachedFileList[pIndex]);}this.rebuildGrid();}/**
2682
+ * Open an entry (navigate into folder or select file).
2683
+ *
2684
+ * @param {number} pIndex - The index in the cached file list
2685
+ */openEntry(pIndex){if(pIndex<0||pIndex>=this._cachedFileList.length){return;}let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(tmpListProvider){tmpListProvider.openEntry(this._cachedFileList[pIndex]);}this.rebuildGrid();this.rebuildBreadcrumb();}/**
2686
+ * Navigate to a path via breadcrumb.
2687
+ *
2688
+ * @param {string} pPath - The target path
2689
+ */navigateToPath(pPath){let tmpBrowseProvider=this.pict.providers['Pict-FileBrowser-Browse'];if(tmpBrowseProvider){tmpBrowseProvider.navigateToFolder(pPath);}this.rebuildGrid();this.rebuildBreadcrumb();}/**
2690
+ * Get the current location from state.
2691
+ *
2692
+ * @returns {string} The current location path
2693
+ */getCurrentLocation(){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses.CurrentLocation||'AppData.PictFileBrowser.CurrentLocation';return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress)||'';}}module.exports=PictViewFileBrowserListIcons;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],62:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ViewFileInfo","DefaultRenderable":"ViewFileInfo-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ViewPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ViewFileInfo-Container-Template","Template":/*html*/`<div class="pict-fb-fileinfo" id="Pict-FileBrowser-FileInfo"></div>`},{"Hash":"FileBrowser-ViewFileInfo-Detail-Template","Template":/*html*/`
2694
+ <div class="pict-fb-fileinfo-title">{~D:Record.Name~}</div>
2695
+ <table class="pict-fb-fileinfo-table">
2696
+ <tr>
2697
+ <td class="pict-fb-fileinfo-label">Type</td>
2698
+ <td class="pict-fb-fileinfo-value">{~D:Record.TypeDescription~}</td>
2699
+ </tr>
2700
+ <tr>
2701
+ <td class="pict-fb-fileinfo-label">Size</td>
2702
+ <td class="pict-fb-fileinfo-value">{~D:Record.SizeFormatted~}</td>
2703
+ </tr>
2704
+ <tr>
2705
+ <td class="pict-fb-fileinfo-label">Modified</td>
2706
+ <td class="pict-fb-fileinfo-value">{~D:Record.ModifiedFormatted~}</td>
2707
+ </tr>
2708
+ <tr>
2709
+ <td class="pict-fb-fileinfo-label">Extension</td>
2710
+ <td class="pict-fb-fileinfo-value">{~D:Record.Extension~}</td>
2711
+ </tr>
2712
+ <tr>
2713
+ <td class="pict-fb-fileinfo-label">Path</td>
2714
+ <td class="pict-fb-fileinfo-value">{~D:Record.Path~}</td>
2715
+ </tr>
2716
+ </table>
2717
+ `},{"Hash":"FileBrowser-ViewFileInfo-Empty-Template","Template":/*html*/`<div class="pict-fb-fileinfo-none">No file selected</div>`}],"Renderables":[{"RenderableHash":"ViewFileInfo-Container","TemplateHash":"FileBrowser-ViewFileInfo-Container-Template","DestinationAddress":"#Pict-FileBrowser-ViewPane","RenderMethod":"replace"}]};/**
2718
+ * Viewing view that shows file metadata/stats for the currently selected file.
2719
+ *
2720
+ * Displays name, type description, size, modification date, extension, and path
2721
+ * in a simple table layout. Shows "No file selected" when nothing is selected.
2722
+ */class PictViewFileBrowserViewFileInfo extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_ViewConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);}/**
2723
+ * After rendering the container, populate with file info.
2724
+ */onAfterRender(pRenderable){this.rebuildFileInfo();this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
2725
+ * Rebuild the file info display.
2726
+ */rebuildFileInfo(){let tmpViewProvider=this.pict.providers['Pict-FileBrowser-View'];let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];let tmpCurrentFile=tmpViewProvider?tmpViewProvider.getCurrentFile():null;if(!tmpCurrentFile){let tmpEmptyHTML=this.pict.parseTemplateByHash('FileBrowser-ViewFileInfo-Empty-Template',{});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-FileInfo',tmpEmptyHTML);return;}let tmpRecord={Name:tmpCurrentFile.Name||'Unknown',TypeDescription:tmpViewProvider?tmpViewProvider.getFileTypeDescription(tmpCurrentFile):'File',SizeFormatted:tmpListProvider?tmpListProvider.formatFileSize(tmpCurrentFile.Size):tmpCurrentFile.Size||'--',ModifiedFormatted:tmpListProvider?tmpListProvider.formatDate(tmpCurrentFile.Modified):tmpCurrentFile.Modified||'--',Extension:tmpCurrentFile.Extension||'--',Path:tmpCurrentFile.Path||'--'};let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewFileInfo-Detail-Template',tmpRecord);this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-FileInfo',tmpHTML);}}module.exports=PictViewFileBrowserViewFileInfo;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],63:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ViewImage","DefaultRenderable":"ViewImage-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ViewPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ViewImage-Container-Template","Template":/*html*/`<div class="pict-fb-image-viewer" id="Pict-FileBrowser-ImageViewer"></div>`},{"Hash":"FileBrowser-ViewImage-Display-Template","Template":/*html*/`<img src="{~D:Record.ImageURL~}" alt="{~D:Record.Name~}" />`},{"Hash":"FileBrowser-ViewImage-NoImage-Template","Template":/*html*/`<div class="pict-fb-image-viewer-none">{~D:Record.Message~}</div>`}],"Renderables":[{"RenderableHash":"ViewImage-Container","TemplateHash":"FileBrowser-ViewImage-Container-Template","DestinationAddress":"#Pict-FileBrowser-ViewPane","RenderMethod":"replace"}]};/**
2727
+ * Viewing view that displays an image preview for the currently selected file.
2728
+ *
2729
+ * If the selected file is an image and has a URL or ThumbnailURL, it renders
2730
+ * an <img> tag. Otherwise shows a "not an image" or "no file selected" message.
2731
+ */class PictViewFileBrowserViewImage extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_ViewConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);}/**
2732
+ * After rendering the container, populate with the image or message.
2733
+ */onAfterRender(pRenderable){this.rebuildImageView();this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
2734
+ * Rebuild the image viewer display.
2735
+ */rebuildImageView(){let tmpViewProvider=this.pict.providers['Pict-FileBrowser-View'];let tmpCurrentFile=tmpViewProvider?tmpViewProvider.getCurrentFile():null;if(!tmpCurrentFile){let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewImage-NoImage-Template',{Message:'No file selected'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ImageViewer',tmpHTML);return;}let tmpIsImage=tmpViewProvider?tmpViewProvider.isImage(tmpCurrentFile):false;if(!tmpIsImage){let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewImage-NoImage-Template',{Message:'Selected file is not an image'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ImageViewer',tmpHTML);return;}let tmpImageURL=tmpViewProvider?tmpViewProvider.getImageURL(tmpCurrentFile):null;if(!tmpImageURL){let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewImage-NoImage-Template',{Message:'No image URL available'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ImageViewer',tmpHTML);return;}let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewImage-Display-Template',{ImageURL:tmpImageURL,Name:tmpCurrentFile.Name||'Image'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ImageViewer',tmpHTML);}}module.exports=PictViewFileBrowserViewImage;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],64:[function(require,module,exports){const libPictView=require('pict-view');const _DefaultConfiguration=require('../Pict-Section-FileBrowser-DefaultConfiguration.js');const libBrowseProvider=require('../providers/Pict-Provider-FileBrowserBrowse.js');const libListProvider=require('../providers/Pict-Provider-FileBrowserList.js');const libViewProvider=require('../providers/Pict-Provider-FileBrowserView.js');const libLayoutProvider=require('../providers/Pict-Provider-FileBrowserLayout.js');const libIconProvider=require('../providers/Pict-Provider-FileBrowserIcons.js');/**
2736
+ * Main FileBrowser view.
2737
+ *
2738
+ * Renders the outer container layout (browse pane, list pane, view pane)
2739
+ * and manages the lifecycle of the three base providers.
2740
+ *
2741
+ * The consuming application registers this view and then adds whichever
2742
+ * browsing, listing, and viewing sub-views they want (tree, search,
2743
+ * detail list, icon grid, file info, image viewer, etc.).
2744
+ *
2745
+ * On initialization, this view ensures the three providers are registered
2746
+ * and that default state values are populated in AppData.
2747
+ */class PictViewFileBrowser extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},JSON.parse(JSON.stringify(_DefaultConfiguration)),pOptions);super(pFable,tmpOptions,pServiceHash);// Register providers if not already registered
2748
+ if(!this.pict.providers['Pict-FileBrowser-Browse']){this.pict.addProvider('Pict-FileBrowser-Browse',libBrowseProvider.default_configuration,libBrowseProvider);}if(!this.pict.providers['Pict-FileBrowser-List']){this.pict.addProvider('Pict-FileBrowser-List',libListProvider.default_configuration,libListProvider);}if(!this.pict.providers['Pict-FileBrowser-View']){this.pict.addProvider('Pict-FileBrowser-View',libViewProvider.default_configuration,libViewProvider);}if(!this.pict.providers['Pict-FileBrowser-Layout']){this.pict.addProvider('Pict-FileBrowser-Layout',libLayoutProvider.default_configuration,libLayoutProvider);}if(!this.pict.providers['Pict-FileBrowser-Icons']){this.pict.addProvider('Pict-FileBrowser-Icons',libIconProvider.default_configuration,libIconProvider);}// Ensure default state in AppData
2749
+ this.ensureDefaultState();}/**
2750
+ * Populate AppData with default state values if not already set.
2751
+ */ensureDefaultState(){let tmpDefaultState=this.options.DefaultState||{};let tmpStateAddresses=this.options.StateAddresses||{};let tmpScope={AppData:this.pict.AppData,Pict:this.pict};// Ensure the PictFileBrowser namespace exists
2752
+ if(!this.pict.AppData.PictFileBrowser){this.pict.AppData.PictFileBrowser={};}for(let tmpKey in tmpStateAddresses){let tmpAddress=tmpStateAddresses[tmpKey];let tmpCurrentValue=this.pict.manifest.getValueByHash(tmpScope,tmpAddress);if(tmpCurrentValue===undefined||tmpCurrentValue===null){let tmpDefault=tmpDefaultState[tmpKey];if(tmpDefault!==undefined){this.pict.manifest.setValueByHash(tmpScope,tmpAddress,tmpDefault);}}}// Ensure FileList and FolderTree arrays exist
2753
+ if(!this.pict.AppData.PictFileBrowser.FileList){this.pict.AppData.PictFileBrowser.FileList=[];}if(!this.pict.AppData.PictFileBrowser.FolderTree){this.pict.AppData.PictFileBrowser.FolderTree=[];}}/**
2754
+ * After rendering the container, inject CSS.
2755
+ */onAfterRender(pRenderable){this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
2756
+ * Get a state value by key.
2757
+ *
2758
+ * @param {string} pKey - One of: Layout, RootLocation, CurrentLocation, CurrentFile
2759
+ * @returns {*} The state value
2760
+ */getState(pKey){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses[pKey];if(!tmpAddress){return undefined;}return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress);}/**
2761
+ * Set a state value by key.
2762
+ *
2763
+ * @param {string} pKey - One of: Layout, RootLocation, CurrentLocation, CurrentFile
2764
+ * @param {*} pValue - The value to set
2765
+ */setState(pKey,pValue){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses[pKey];if(!tmpAddress){return;}this.pict.manifest.setValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress,pValue);}}module.exports=PictViewFileBrowser;module.exports.default_configuration=_DefaultConfiguration;},{"../Pict-Section-FileBrowser-DefaultConfiguration.js":50,"../providers/Pict-Provider-FileBrowserBrowse.js":52,"../providers/Pict-Provider-FileBrowserIcons.js":53,"../providers/Pict-Provider-FileBrowserLayout.js":54,"../providers/Pict-Provider-FileBrowserList.js":55,"../providers/Pict-Provider-FileBrowserView.js":56,"pict-view":76}],65:[function(require,module,exports){// The container for all the Pict-Section-Content related code.
2766
+ // The main content view class
2767
+ module.exports=require('./views/Pict-View-Content.js');// The content provider (markdown parsing, HTML escaping)
2768
+ module.exports.PictContentProvider=require('./providers/Pict-Provider-Content.js');},{"./providers/Pict-Provider-Content.js":66,"./views/Pict-View-Content.js":67}],66:[function(require,module,exports){const libPictProvider=require('pict-provider');const libCreateHighlighter=require('pict-section-code').createHighlighter;/**
2769
+ * Content Provider for Pict Section Content
2770
+ *
2771
+ * A general-purpose markdown-to-HTML parser with support for:
2772
+ * - Headings, paragraphs, lists, blockquotes, horizontal rules
2773
+ * - Fenced code blocks with language tags (nested fence support)
2774
+ * - Syntax highlighting and line numbers for code blocks (via pict-section-code)
2775
+ * - Tables (GFM pipe syntax)
2776
+ * - Mermaid diagram blocks
2777
+ * - KaTeX math (inline and display)
2778
+ * - Bold, italic, inline code, links, images
2779
+ *
2780
+ * Link resolution is customizable via an optional callback.
2781
+ */class PictContentProvider extends libPictProvider{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);}/**
2782
+ * Highlight a code string using pict-section-code's syntax highlighter.
2783
+ * Uses a mock element to interface with the highlighter's DOM-based API.
2784
+ *
2785
+ * @param {string} pCode - The raw code string
2786
+ * @param {string} pLanguage - The language identifier (e.g. "javascript", "html")
2787
+ * @returns {string} The syntax-highlighted HTML
2788
+ */highlightCode(pCode,pLanguage){if(!pCode){return'';}let tmpHighlighter=libCreateHighlighter(pLanguage);// Create a mock element to interface with the highlighter
2789
+ let tmpMockElement={textContent:pCode,innerHTML:''};tmpHighlighter(tmpMockElement);return tmpMockElement.innerHTML;}/**
2790
+ * Generate line number HTML for a code block.
2791
+ *
2792
+ * @param {string} pCode - The raw code string
2793
+ * @returns {string} HTML string with line number spans
2794
+ */generateLineNumbers(pCode){if(!pCode){return'<span>1</span>';}let tmpLineCount=pCode.split('\n').length;let tmpHTML='';for(let i=1;i<=tmpLineCount;i++){tmpHTML+='<span>'+i+'</span>';}return tmpHTML;}/**
2795
+ * Parse a markdown string into HTML.
2796
+ *
2797
+ * @param {string} pMarkdown - The raw markdown text
2798
+ * @param {Function} [pLinkResolver] - Optional callback for link resolution: (pHref, pLinkText) => { href, target, rel } or null
2799
+ * @param {Function} [pImageResolver] - Optional callback for image URL resolution: (pSrc, pAlt) => resolvedSrc or null
2800
+ * @returns {string} The parsed HTML
2801
+ */parseMarkdown(pMarkdown,pLinkResolver,pImageResolver){if(!pMarkdown){return'';}let tmpLines=pMarkdown.split('\n');let tmpHTML=[];let tmpInCodeBlock=false;let tmpCodeFenceLength=0;let tmpCodeLang='';let tmpCodeLines=[];let tmpInList=false;let tmpListType='';let tmpInBlockquote=false;let tmpBlockquoteLines=[];let tmpInMathBlock=false;let tmpMathLines=[];let tmpParagraphLines=[];// Helper to flush accumulated paragraph lines into a single <p> tag
2802
+ let fFlushParagraph=()=>{if(tmpParagraphLines.length>0){tmpHTML.push('<p>'+tmpParagraphLines.map(pLine=>{return this.parseInline(pLine,pLinkResolver,pImageResolver);}).join(' ')+'</p>');tmpParagraphLines=[];}};for(let i=0;i<tmpLines.length;i++){let tmpLine=tmpLines[i];// Display math blocks ($$...$$) — skip if inside a code block
2803
+ if(!tmpInCodeBlock&&tmpLine.trim().match(/^\$\$/)){if(tmpInMathBlock){// End math block
2804
+ tmpHTML.push('<div class="pict-content-katex-display">'+tmpMathLines.join('\n')+'</div>');tmpInMathBlock=false;tmpMathLines=[];}else{// Flush any pending paragraph
2805
+ fFlushParagraph();// Close any open list or blockquote
2806
+ if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver,pImageResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}tmpInMathBlock=true;}continue;}if(tmpInMathBlock){tmpMathLines.push(tmpLine);continue;}// Code blocks (fenced) — track fence length so ````x```` nests around ```y```
2807
+ let tmpFenceMatch=tmpLine.match(/^(`{3,})/);if(tmpFenceMatch){let tmpFenceLen=tmpFenceMatch[1].length;if(tmpInCodeBlock){// Only close if the closing fence is at least as long as the opening
2808
+ if(tmpFenceLen>=tmpCodeFenceLength&&tmpLine.trim()===tmpFenceMatch[1]){// End code block
2809
+ if(tmpCodeLang==='mermaid'){// Mermaid diagrams: output raw content for client-side rendering
2810
+ tmpHTML.push('<pre class="mermaid">'+tmpCodeLines.join('\n')+'</pre>');}else{let tmpCodeText=tmpCodeLines.join('\n');let tmpHighlightedCode=this.highlightCode(tmpCodeText,tmpCodeLang);let tmpLineNumbersHTML=this.generateLineNumbers(tmpCodeText);tmpHTML.push('<div class="pict-content-code-wrap"><div class="pict-content-code-line-numbers">'+tmpLineNumbersHTML+'</div><pre><code class="language-'+this.escapeHTML(tmpCodeLang)+'">'+tmpHighlightedCode+'</code></pre></div>');}tmpInCodeBlock=false;tmpCodeFenceLength=0;tmpCodeLang='';tmpCodeLines=[];continue;}else{// Inner fence with fewer backticks — treat as content
2811
+ tmpCodeLines.push(tmpLine);continue;}}else{// Flush any pending paragraph
2812
+ fFlushParagraph();// Close any open list or blockquote
2813
+ if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver,pImageResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}// Start code block — record fence length
2814
+ tmpCodeFenceLength=tmpFenceLen;tmpCodeLang=tmpLine.replace(/^`{3,}/,'').trim();tmpInCodeBlock=true;continue;}}if(tmpInCodeBlock){tmpCodeLines.push(tmpLine);continue;}// Blockquotes
2815
+ if(tmpLine.match(/^>\s?/)){if(!tmpInBlockquote){// Flush any pending paragraph
2816
+ fFlushParagraph();// Close any open list
2817
+ if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}tmpInBlockquote=true;tmpBlockquoteLines=[];}tmpBlockquoteLines.push(tmpLine.replace(/^>\s?/,''));continue;}else if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver,pImageResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}// Horizontal rule
2818
+ if(tmpLine.match(/^(-{3,}|\*{3,}|_{3,})\s*$/)){fFlushParagraph();if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}tmpHTML.push('<hr>');continue;}// Headings
2819
+ let tmpHeadingMatch=tmpLine.match(/^(#{1,6})\s+(.+)/);if(tmpHeadingMatch){fFlushParagraph();if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}let tmpLevel=tmpHeadingMatch[1].length;let tmpText=this.parseInline(tmpHeadingMatch[2],pLinkResolver,pImageResolver);let tmpID=tmpHeadingMatch[2].toLowerCase().replace(/[^\w\s-]/g,'').replace(/\s+/g,'-');tmpHTML.push('<h'+tmpLevel+' id="'+tmpID+'">'+tmpText+'</h'+tmpLevel+'>');continue;}// Unordered list items
2820
+ let tmpULMatch=tmpLine.match(/^(\s*)[-*+]\s+(.*)/);if(tmpULMatch){fFlushParagraph();if(!tmpInList||tmpListType!=='ul'){if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}tmpHTML.push('<ul>');tmpInList=true;tmpListType='ul';}tmpHTML.push('<li>'+this.parseInline(tmpULMatch[2],pLinkResolver,pImageResolver)+'</li>');continue;}// Ordered list items
2821
+ let tmpOLMatch=tmpLine.match(/^(\s*)\d+\.\s+(.*)/);if(tmpOLMatch){fFlushParagraph();if(!tmpInList||tmpListType!=='ol'){if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}tmpHTML.push('<ol>');tmpInList=true;tmpListType='ol';}tmpHTML.push('<li>'+this.parseInline(tmpOLMatch[2],pLinkResolver,pImageResolver)+'</li>');continue;}// Close list if we've left list items
2822
+ if(tmpInList&&tmpLine.trim()!==''){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}// Empty line — flush any accumulated paragraph
2823
+ if(tmpLine.trim()===''){fFlushParagraph();continue;}// Table detection
2824
+ if(tmpLine.match(/^\|/)&&i+1<tmpLines.length&&tmpLines[i+1].match(/^\|[\s-:|]+\|/)){fFlushParagraph();// Close any open list
2825
+ if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}let tmpTableHTML='<table>';// Header row
2826
+ let tmpHeaders=tmpLine.split('|').filter(pCell=>{return pCell.trim()!=='';});tmpTableHTML+='<thead><tr>';for(let h=0;h<tmpHeaders.length;h++){tmpTableHTML+='<th>'+this.parseInline(tmpHeaders[h].trim(),pLinkResolver,pImageResolver)+'</th>';}tmpTableHTML+='</tr></thead>';// Skip separator row
2827
+ i++;// Body rows
2828
+ tmpTableHTML+='<tbody>';while(i+1<tmpLines.length&&tmpLines[i+1].match(/^\|/)){i++;let tmpCells=tmpLines[i].split('|').filter(pCell=>{return pCell.trim()!=='';});tmpTableHTML+='<tr>';for(let c=0;c<tmpCells.length;c++){tmpTableHTML+='<td>'+this.parseInline(tmpCells[c].trim(),pLinkResolver,pImageResolver)+'</td>';}tmpTableHTML+='</tr>';}tmpTableHTML+='</tbody></table>';tmpHTML.push(tmpTableHTML);continue;}// Accumulate paragraph lines — consecutive non-blank text lines
2829
+ // will be joined into a single <p> tag when flushed
2830
+ tmpParagraphLines.push(tmpLine);}// Flush any remaining accumulated paragraph
2831
+ fFlushParagraph();// Close any trailing open elements
2832
+ if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver,pImageResolver)+'</blockquote>');}if(tmpInCodeBlock){let tmpCodeText=tmpCodeLines.join('\n');let tmpHighlightedCode=this.highlightCode(tmpCodeText,tmpCodeLang);let tmpLineNumbersHTML=this.generateLineNumbers(tmpCodeText);tmpHTML.push('<div class="pict-content-code-wrap"><div class="pict-content-code-line-numbers">'+tmpLineNumbersHTML+'</div><pre><code>'+tmpHighlightedCode+'</code></pre></div>');}return tmpHTML.join('\n');}/**
2833
+ * Parse inline markdown elements (bold, italic, code, links, images, KaTeX).
2834
+ *
2835
+ * @param {string} pText - The text to parse
2836
+ * @param {Function} [pLinkResolver] - Optional callback: (pHref, pLinkText) => { href, target, rel } or null
2837
+ * @param {Function} [pImageResolver] - Optional callback: (pSrc, pAlt) => resolvedSrc or null
2838
+ * @returns {string} HTML with inline elements
2839
+ */parseInline(pText,pLinkResolver,pImageResolver){if(!pText){return'';}let tmpResult=pText;// Extract inline code spans into placeholders so bold/italic regexes don't mangle their contents
2840
+ let tmpCodeSpans=[];tmpResult=tmpResult.replace(/`([^`]+)`/g,(pMatch,pCode)=>{let tmpIndex=tmpCodeSpans.length;tmpCodeSpans.push('<code>'+pCode+'</code>');return'\x00CODEINLINE'+tmpIndex+'\x00';});// Inline LaTeX equations ($...$) — must be processed before other inline patterns
2841
+ // Match single $ delimiters that aren't adjacent to spaces (to avoid false positives with currency)
2842
+ tmpResult=tmpResult.replace(/\$([^\$\s][^\$]*?[^\$\s])\$/g,'<span class="pict-content-katex-inline">$1</span>');// Also match single-character inline math like $x$
2843
+ tmpResult=tmpResult.replace(/\$([^\$\s])\$/g,'<span class="pict-content-katex-inline">$1</span>');// Images
2844
+ tmpResult=tmpResult.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(pMatch,pAlt,pSrc)=>{let tmpSrc=pSrc;if(typeof pImageResolver==='function'){let tmpResolved=pImageResolver(pSrc,pAlt);if(tmpResolved){tmpSrc=tmpResolved;}}return'<img src="'+tmpSrc+'" alt="'+pAlt+'">';});// Links
2845
+ tmpResult=tmpResult.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(pMatch,pLinkText,pHref)=>{if(typeof pLinkResolver==='function'){let tmpResolved=pLinkResolver(pHref,pLinkText);if(tmpResolved){let tmpTarget=tmpResolved.target?' target="'+tmpResolved.target+'"':'';let tmpRel=tmpResolved.rel?' rel="'+tmpResolved.rel+'"':'';return'<a href="'+tmpResolved.href+'"'+tmpTarget+tmpRel+'>'+pLinkText+'</a>';}}// Default behavior: external links open in new tab
2846
+ if(pHref.match(/^https?:\/\//)){return'<a href="'+pHref+'" target="_blank" rel="noopener">'+pLinkText+'</a>';}return'<a href="'+pHref+'">'+pLinkText+'</a>';});// Bold
2847
+ tmpResult=tmpResult.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');tmpResult=tmpResult.replace(/__([^_]+)__/g,'<strong>$1</strong>');// Italic
2848
+ tmpResult=tmpResult.replace(/\*([^*]+)\*/g,'<em>$1</em>');tmpResult=tmpResult.replace(/_([^_]+)_/g,'<em>$1</em>');// Restore inline code spans from placeholders
2849
+ tmpResult=tmpResult.replace(/\x00CODEINLINE(\d+)\x00/g,(pMatch,pIndex)=>{return tmpCodeSpans[parseInt(pIndex)];});return tmpResult;}/**
2850
+ * Escape HTML special characters.
2851
+ *
2852
+ * @param {string} pText - The text to escape
2853
+ * @returns {string} The escaped text
2854
+ */escapeHTML(pText){if(!pText){return'';}return pText.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}}module.exports=PictContentProvider;module.exports.default_configuration={ProviderIdentifier:"Pict-Content",AutoInitialize:true,AutoInitializeOrdinal:0};},{"pict-provider":46,"pict-section-code":49}],67:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={ViewIdentifier:"Pict-Content",DefaultRenderable:"Pict-Content-Display",DefaultDestinationAddress:"#Pict-Content-Container",AutoRender:false,CSS:/*css*/`
2855
+ .pict-content {
2856
+ padding: 2em 3em;
2857
+ max-width: 900px;
2858
+ margin: 0 auto;
2859
+ }
2860
+ .pict-content-loading {
2861
+ display: flex;
2862
+ align-items: center;
2863
+ justify-content: center;
2864
+ min-height: 200px;
2865
+ color: #8A7F72;
2866
+ font-size: 1em;
2867
+ }
2868
+ .pict-content h1 {
2869
+ font-size: 2em;
2870
+ color: #3D3229;
2871
+ border-bottom: 1px solid #DDD6CA;
2872
+ padding-bottom: 0.3em;
2873
+ margin-top: 0;
2874
+ }
2875
+ .pict-content h2 {
2876
+ font-size: 1.5em;
2877
+ color: #3D3229;
2878
+ border-bottom: 1px solid #EAE3D8;
2879
+ padding-bottom: 0.25em;
2880
+ margin-top: 1.5em;
2881
+ }
2882
+ .pict-content h3 {
2883
+ font-size: 1.25em;
2884
+ color: #3D3229;
2885
+ margin-top: 1.25em;
2886
+ }
2887
+ .pict-content h4, .pict-content h5, .pict-content h6 {
2888
+ color: #5E5549;
2889
+ margin-top: 1em;
2890
+ }
2891
+ .pict-content p {
2892
+ line-height: 1.7;
2893
+ color: #423D37;
2894
+ margin: 0.75em 0;
2895
+ }
2896
+ .pict-content a {
2897
+ color: #2E7D74;
2898
+ text-decoration: none;
2899
+ }
2900
+ .pict-content a:hover {
2901
+ text-decoration: underline;
2902
+ }
2903
+ .pict-content-code-wrap {
2904
+ position: relative;
2905
+ font-family: 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
2906
+ font-size: 14px;
2907
+ line-height: 1.5;
2908
+ border-radius: 6px;
2909
+ overflow: auto;
2910
+ margin: 1em 0;
2911
+ background: #3D3229;
2912
+ }
2913
+ .pict-content-code-wrap .pict-content-code-line-numbers {
2914
+ position: absolute;
2915
+ top: 0;
2916
+ left: 0;
2917
+ width: 40px;
2918
+ padding: 1.25em 0;
2919
+ text-align: right;
2920
+ background: #342A22;
2921
+ border-right: 1px solid #4A3F35;
2922
+ color: #8A7F72;
2923
+ font-size: 13px;
2924
+ line-height: 1.5;
2925
+ user-select: none;
2926
+ pointer-events: none;
2927
+ box-sizing: border-box;
2928
+ }
2929
+ .pict-content-code-wrap .pict-content-code-line-numbers span {
2930
+ display: block;
2931
+ padding: 0 8px 0 0;
2932
+ }
2933
+ .pict-content-code-wrap pre {
2934
+ margin: 0;
2935
+ background: #3D3229;
2936
+ color: #E8E0D4;
2937
+ padding: 1.25em 1.25em 1.25em 52px;
2938
+ border-radius: 6px;
2939
+ overflow-x: auto;
2940
+ line-height: 1.5;
2941
+ font-size: inherit;
2942
+ }
2943
+ .pict-content-code-wrap pre code {
2944
+ background: none;
2945
+ padding: 0;
2946
+ color: inherit;
2947
+ font-size: inherit;
2948
+ font-family: inherit;
2949
+ }
2950
+ .pict-content-code-wrap .keyword { color: #C678DD; }
2951
+ .pict-content-code-wrap .string { color: #98C379; }
2952
+ .pict-content-code-wrap .number { color: #D19A66; }
2953
+ .pict-content-code-wrap .comment { color: #7F848E; font-style: italic; }
2954
+ .pict-content-code-wrap .operator { color: #56B6C2; }
2955
+ .pict-content-code-wrap .punctuation { color: #E8E0D4; }
2956
+ .pict-content-code-wrap .function-name { color: #61AFEF; }
2957
+ .pict-content-code-wrap .property { color: #E06C75; }
2958
+ .pict-content-code-wrap .tag { color: #E06C75; }
2959
+ .pict-content-code-wrap .attr-name { color: #D19A66; }
2960
+ .pict-content-code-wrap .attr-value { color: #98C379; }
2961
+ .pict-content pre {
2962
+ background: #3D3229;
2963
+ color: #E8E0D4;
2964
+ padding: 1.25em;
2965
+ border-radius: 6px;
2966
+ overflow-x: auto;
2967
+ line-height: 1.5;
2968
+ font-size: 0.9em;
2969
+ }
2970
+ .pict-content code {
2971
+ background: #F0ECE4;
2972
+ padding: 0.15em 0.4em;
2973
+ border-radius: 3px;
2974
+ font-size: 0.9em;
2975
+ color: #9E6B47;
2976
+ }
2977
+ .pict-content pre code {
2978
+ background: none;
2979
+ padding: 0;
2980
+ color: inherit;
2981
+ font-size: inherit;
2982
+ }
2983
+ .pict-content blockquote {
2984
+ border-left: 4px solid #2E7D74;
2985
+ margin: 1em 0;
2986
+ padding: 0.5em 1em;
2987
+ background: #F7F5F0;
2988
+ color: #5E5549;
2989
+ }
2990
+ .pict-content blockquote p {
2991
+ margin: 0.25em 0;
2992
+ }
2993
+ .pict-content ul, .pict-content ol {
2994
+ padding-left: 2em;
2995
+ line-height: 1.8;
2996
+ }
2997
+ .pict-content li {
2998
+ margin: 0.25em 0;
2999
+ color: #423D37;
3000
+ }
3001
+ .pict-content hr {
3002
+ border: none;
3003
+ border-top: 1px solid #DDD6CA;
3004
+ margin: 2em 0;
3005
+ }
3006
+ .pict-content table {
3007
+ width: 100%;
3008
+ border-collapse: collapse;
3009
+ margin: 1em 0;
3010
+ }
3011
+ .pict-content table th {
3012
+ background: #F5F0E8;
3013
+ border: 1px solid #DDD6CA;
3014
+ padding: 0.6em 0.8em;
3015
+ text-align: left;
3016
+ font-weight: 600;
3017
+ color: #3D3229;
3018
+ }
3019
+ .pict-content table td {
3020
+ border: 1px solid #DDD6CA;
3021
+ padding: 0.5em 0.8em;
3022
+ color: #423D37;
3023
+ }
3024
+ .pict-content table tr:nth-child(even) {
3025
+ background: #F7F5F0;
3026
+ }
3027
+ .pict-content img {
3028
+ max-width: 100%;
3029
+ height: auto;
3030
+ }
3031
+ .pict-content pre.mermaid {
3032
+ background: #fff;
3033
+ color: #3D3229;
3034
+ text-align: center;
3035
+ padding: 1em;
3036
+ }
3037
+ .pict-content .pict-content-katex-display {
3038
+ text-align: center;
3039
+ margin: 1em 0;
3040
+ padding: 0.5em;
3041
+ overflow-x: auto;
3042
+ }
3043
+ .pict-content .pict-content-katex-inline {
3044
+ display: inline;
3045
+ }
3046
+ `,Templates:[{Hash:"Pict-Content-Template",Template:/*html*/`
3047
+ <div class="pict-content" id="Pict-Content-Body">
3048
+ <div class="pict-content-loading">Loading content...</div>
3049
+ </div>
3050
+ `}],Renderables:[{RenderableHash:"Pict-Content-Display",TemplateHash:"Pict-Content-Template",DestinationAddress:"#Pict-Content-Container",RenderMethod:"replace"}]};class PictContentView extends libPictView{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);}/**
3051
+ * Display parsed HTML content in the content area.
2997
3052
  *
2998
- * @param {string} pPath - The target path
2999
- */navigateToPath(pPath){let tmpBrowseProvider=this.pict.providers['Pict-FileBrowser-Browse'];if(tmpBrowseProvider){tmpBrowseProvider.navigateToFolder(pPath);}this.rebuildGrid();this.rebuildBreadcrumb();}/**
3000
- * Get the current location from state.
3053
+ * @param {string} pHTMLContent - The HTML to display
3054
+ * @param {string} [pContainerID] - The container element ID (defaults to 'Pict-Content-Body')
3055
+ */displayContent(pHTMLContent,pContainerID){let tmpContainerID=pContainerID||'Pict-Content-Body';this.pict.ContentAssignment.assignContent('#'+tmpContainerID,pHTMLContent);// Scroll to top of content area
3056
+ let tmpContentContainer=document.getElementById(tmpContainerID);if(tmpContentContainer&&tmpContentContainer.parentElement){tmpContentContainer.parentElement.scrollTop=0;}// Post-render: initialize Mermaid diagrams if mermaid is available
3057
+ this.renderMermaidDiagrams(tmpContainerID);// Post-render: render KaTeX equations if katex is available
3058
+ this.renderKaTeXEquations(tmpContainerID);}/**
3059
+ * Render any Mermaid diagram blocks in the content area.
3060
+ * Mermaid blocks are `<pre class="mermaid">` elements produced by parseMarkdown.
3001
3061
  *
3002
- * @returns {string} The current location path
3003
- */getCurrentLocation(){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses.CurrentLocation||'AppData.PictFileBrowser.CurrentLocation';return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress)||'';}}module.exports=PictViewFileBrowserListIcons;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],65:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ViewFileInfo","DefaultRenderable":"ViewFileInfo-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ViewPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ViewFileInfo-Container-Template","Template":/*html*/`<div class="pict-fb-fileinfo" id="Pict-FileBrowser-FileInfo"></div>`},{"Hash":"FileBrowser-ViewFileInfo-Detail-Template","Template":/*html*/`
3004
- <div class="pict-fb-fileinfo-title">{~D:Record.Name~}</div>
3005
- <table class="pict-fb-fileinfo-table">
3006
- <tr>
3007
- <td class="pict-fb-fileinfo-label">Type</td>
3008
- <td class="pict-fb-fileinfo-value">{~D:Record.TypeDescription~}</td>
3009
- </tr>
3010
- <tr>
3011
- <td class="pict-fb-fileinfo-label">Size</td>
3012
- <td class="pict-fb-fileinfo-value">{~D:Record.SizeFormatted~}</td>
3013
- </tr>
3014
- <tr>
3015
- <td class="pict-fb-fileinfo-label">Modified</td>
3016
- <td class="pict-fb-fileinfo-value">{~D:Record.ModifiedFormatted~}</td>
3017
- </tr>
3018
- <tr>
3019
- <td class="pict-fb-fileinfo-label">Extension</td>
3020
- <td class="pict-fb-fileinfo-value">{~D:Record.Extension~}</td>
3021
- </tr>
3022
- <tr>
3023
- <td class="pict-fb-fileinfo-label">Path</td>
3024
- <td class="pict-fb-fileinfo-value">{~D:Record.Path~}</td>
3025
- </tr>
3026
- </table>
3027
- `},{"Hash":"FileBrowser-ViewFileInfo-Empty-Template","Template":/*html*/`<div class="pict-fb-fileinfo-none">No file selected</div>`}],"Renderables":[{"RenderableHash":"ViewFileInfo-Container","TemplateHash":"FileBrowser-ViewFileInfo-Container-Template","DestinationAddress":"#Pict-FileBrowser-ViewPane","RenderMethod":"replace"}]};/**
3028
- * Viewing view that shows file metadata/stats for the currently selected file.
3029
- *
3030
- * Displays name, type description, size, modification date, extension, and path
3031
- * in a simple table layout. Shows "No file selected" when nothing is selected.
3032
- */class PictViewFileBrowserViewFileInfo extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_ViewConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);}/**
3033
- * After rendering the container, populate with file info.
3034
- */onAfterRender(pRenderable){this.rebuildFileInfo();this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
3035
- * Rebuild the file info display.
3036
- */rebuildFileInfo(){let tmpViewProvider=this.pict.providers['Pict-FileBrowser-View'];let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];let tmpCurrentFile=tmpViewProvider?tmpViewProvider.getCurrentFile():null;if(!tmpCurrentFile){let tmpEmptyHTML=this.pict.parseTemplateByHash('FileBrowser-ViewFileInfo-Empty-Template',{});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-FileInfo',tmpEmptyHTML);return;}let tmpRecord={Name:tmpCurrentFile.Name||'Unknown',TypeDescription:tmpViewProvider?tmpViewProvider.getFileTypeDescription(tmpCurrentFile):'File',SizeFormatted:tmpListProvider?tmpListProvider.formatFileSize(tmpCurrentFile.Size):tmpCurrentFile.Size||'--',ModifiedFormatted:tmpListProvider?tmpListProvider.formatDate(tmpCurrentFile.Modified):tmpCurrentFile.Modified||'--',Extension:tmpCurrentFile.Extension||'--',Path:tmpCurrentFile.Path||'--'};let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewFileInfo-Detail-Template',tmpRecord);this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-FileInfo',tmpHTML);}}module.exports=PictViewFileBrowserViewFileInfo;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],66:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={"ViewIdentifier":"Pict-FileBrowser-ViewImage","DefaultRenderable":"ViewImage-Container","DefaultDestinationAddress":"#Pict-FileBrowser-ViewPane","AutoRender":false,"Templates":[{"Hash":"FileBrowser-ViewImage-Container-Template","Template":/*html*/`<div class="pict-fb-image-viewer" id="Pict-FileBrowser-ImageViewer"></div>`},{"Hash":"FileBrowser-ViewImage-Display-Template","Template":/*html*/`<img src="{~D:Record.ImageURL~}" alt="{~D:Record.Name~}" />`},{"Hash":"FileBrowser-ViewImage-NoImage-Template","Template":/*html*/`<div class="pict-fb-image-viewer-none">{~D:Record.Message~}</div>`}],"Renderables":[{"RenderableHash":"ViewImage-Container","TemplateHash":"FileBrowser-ViewImage-Container-Template","DestinationAddress":"#Pict-FileBrowser-ViewPane","RenderMethod":"replace"}]};/**
3037
- * Viewing view that displays an image preview for the currently selected file.
3038
- *
3039
- * If the selected file is an image and has a URL or ThumbnailURL, it renders
3040
- * an <img> tag. Otherwise shows a "not an image" or "no file selected" message.
3041
- */class PictViewFileBrowserViewImage extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_ViewConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);}/**
3042
- * After rendering the container, populate with the image or message.
3043
- */onAfterRender(pRenderable){this.rebuildImageView();this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
3044
- * Rebuild the image viewer display.
3045
- */rebuildImageView(){let tmpViewProvider=this.pict.providers['Pict-FileBrowser-View'];let tmpCurrentFile=tmpViewProvider?tmpViewProvider.getCurrentFile():null;if(!tmpCurrentFile){let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewImage-NoImage-Template',{Message:'No file selected'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ImageViewer',tmpHTML);return;}let tmpIsImage=tmpViewProvider?tmpViewProvider.isImage(tmpCurrentFile):false;if(!tmpIsImage){let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewImage-NoImage-Template',{Message:'Selected file is not an image'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ImageViewer',tmpHTML);return;}let tmpImageURL=tmpViewProvider?tmpViewProvider.getImageURL(tmpCurrentFile):null;if(!tmpImageURL){let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewImage-NoImage-Template',{Message:'No image URL available'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ImageViewer',tmpHTML);return;}let tmpHTML=this.pict.parseTemplateByHash('FileBrowser-ViewImage-Display-Template',{ImageURL:tmpImageURL,Name:tmpCurrentFile.Name||'Image'});this.pict.ContentAssignment.assignContent('#Pict-FileBrowser-ImageViewer',tmpHTML);}}module.exports=PictViewFileBrowserViewImage;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],67:[function(require,module,exports){const libPictView=require('pict-view');const _DefaultConfiguration=require('../Pict-Section-FileBrowser-DefaultConfiguration.js');const libBrowseProvider=require('../providers/Pict-Provider-FileBrowserBrowse.js');const libListProvider=require('../providers/Pict-Provider-FileBrowserList.js');const libViewProvider=require('../providers/Pict-Provider-FileBrowserView.js');const libLayoutProvider=require('../providers/Pict-Provider-FileBrowserLayout.js');const libIconProvider=require('../providers/Pict-Provider-FileBrowserIcons.js');/**
3046
- * Main FileBrowser view.
3047
- *
3048
- * Renders the outer container layout (browse pane, list pane, view pane)
3049
- * and manages the lifecycle of the three base providers.
3050
- *
3051
- * The consuming application registers this view and then adds whichever
3052
- * browsing, listing, and viewing sub-views they want (tree, search,
3053
- * detail list, icon grid, file info, image viewer, etc.).
3054
- *
3055
- * On initialization, this view ensures the three providers are registered
3056
- * and that default state values are populated in AppData.
3057
- */class PictViewFileBrowser extends libPictView{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},JSON.parse(JSON.stringify(_DefaultConfiguration)),pOptions);super(pFable,tmpOptions,pServiceHash);// Register providers if not already registered
3058
- if(!this.pict.providers['Pict-FileBrowser-Browse']){this.pict.addProvider('Pict-FileBrowser-Browse',libBrowseProvider.default_configuration,libBrowseProvider);}if(!this.pict.providers['Pict-FileBrowser-List']){this.pict.addProvider('Pict-FileBrowser-List',libListProvider.default_configuration,libListProvider);}if(!this.pict.providers['Pict-FileBrowser-View']){this.pict.addProvider('Pict-FileBrowser-View',libViewProvider.default_configuration,libViewProvider);}if(!this.pict.providers['Pict-FileBrowser-Layout']){this.pict.addProvider('Pict-FileBrowser-Layout',libLayoutProvider.default_configuration,libLayoutProvider);}if(!this.pict.providers['Pict-FileBrowser-Icons']){this.pict.addProvider('Pict-FileBrowser-Icons',libIconProvider.default_configuration,libIconProvider);}// Ensure default state in AppData
3059
- this.ensureDefaultState();}/**
3060
- * Populate AppData with default state values if not already set.
3061
- */ensureDefaultState(){let tmpDefaultState=this.options.DefaultState||{};let tmpStateAddresses=this.options.StateAddresses||{};let tmpScope={AppData:this.pict.AppData,Pict:this.pict};// Ensure the PictFileBrowser namespace exists
3062
- if(!this.pict.AppData.PictFileBrowser){this.pict.AppData.PictFileBrowser={};}for(let tmpKey in tmpStateAddresses){let tmpAddress=tmpStateAddresses[tmpKey];let tmpCurrentValue=this.pict.manifest.getValueByHash(tmpScope,tmpAddress);if(tmpCurrentValue===undefined||tmpCurrentValue===null){let tmpDefault=tmpDefaultState[tmpKey];if(tmpDefault!==undefined){this.pict.manifest.setValueByHash(tmpScope,tmpAddress,tmpDefault);}}}// Ensure FileList and FolderTree arrays exist
3063
- if(!this.pict.AppData.PictFileBrowser.FileList){this.pict.AppData.PictFileBrowser.FileList=[];}if(!this.pict.AppData.PictFileBrowser.FolderTree){this.pict.AppData.PictFileBrowser.FolderTree=[];}}/**
3064
- * After rendering the container, inject CSS.
3065
- */onAfterRender(pRenderable){this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}/**
3066
- * Get a state value by key.
3062
+ * @param {string} [pContainerID] - The container element ID (defaults to 'Pict-Content-Body')
3063
+ */renderMermaidDiagrams(pContainerID){if(typeof mermaid==='undefined'){return;}let tmpContainerID=pContainerID||'Pict-Content-Body';let tmpContentBody=document.getElementById(tmpContainerID);if(!tmpContentBody){return;}let tmpMermaidElements=tmpContentBody.querySelectorAll('pre.mermaid');if(tmpMermaidElements.length<1){return;}// mermaid.run() will process all pre.mermaid elements in the container
3064
+ try{mermaid.run({nodes:tmpMermaidElements});}catch(pError){this.log.error('Mermaid rendering error: '+pError.message);}}/**
3065
+ * Render KaTeX inline and display math elements in the content area.
3066
+ * Inline: `<span class="pict-content-katex-inline">`
3067
+ * Display: `<div class="pict-content-katex-display">`
3067
3068
  *
3068
- * @param {string} pKey - One of: Layout, RootLocation, CurrentLocation, CurrentFile
3069
- * @returns {*} The state value
3070
- */getState(pKey){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses[pKey];if(!tmpAddress){return undefined;}return this.pict.manifest.getValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress);}/**
3071
- * Set a state value by key.
3069
+ * @param {string} [pContainerID] - The container element ID (defaults to 'Pict-Content-Body')
3070
+ */renderKaTeXEquations(pContainerID){if(typeof katex==='undefined'){return;}let tmpContainerID=pContainerID||'Pict-Content-Body';let tmpContentBody=document.getElementById(tmpContainerID);if(!tmpContentBody){return;}// Render inline math
3071
+ let tmpInlineElements=tmpContentBody.querySelectorAll('.pict-content-katex-inline');for(let i=0;i<tmpInlineElements.length;i++){try{katex.render(tmpInlineElements[i].textContent,tmpInlineElements[i],{throwOnError:false,displayMode:false});}catch(pError){this.log.warn('KaTeX inline error: '+pError.message);}}// Render display math
3072
+ let tmpDisplayElements=tmpContentBody.querySelectorAll('.pict-content-katex-display');for(let i=0;i<tmpDisplayElements.length;i++){try{katex.render(tmpDisplayElements[i].textContent,tmpDisplayElements[i],{throwOnError:false,displayMode:true});}catch(pError){this.log.warn('KaTeX display error: '+pError.message);}}}/**
3073
+ * Show a loading indicator.
3072
3074
  *
3073
- * @param {string} pKey - One of: Layout, RootLocation, CurrentLocation, CurrentFile
3074
- * @param {*} pValue - The value to set
3075
- */setState(pKey,pValue){let tmpStateAddresses=this.options.StateAddresses||{};let tmpAddress=tmpStateAddresses[pKey];if(!tmpAddress){return;}this.pict.manifest.setValueByHash({AppData:this.pict.AppData,Pict:this.pict},tmpAddress,pValue);}}module.exports=PictViewFileBrowser;module.exports.default_configuration=_DefaultConfiguration;},{"../Pict-Section-FileBrowser-DefaultConfiguration.js":53,"../providers/Pict-Provider-FileBrowserBrowse.js":55,"../providers/Pict-Provider-FileBrowserIcons.js":56,"../providers/Pict-Provider-FileBrowserLayout.js":57,"../providers/Pict-Provider-FileBrowserList.js":58,"../providers/Pict-Provider-FileBrowserView.js":59,"pict-view":76}],68:[function(require,module,exports){/**
3075
+ * @param {string} [pMessage] - Loading message (defaults to 'Loading content...')
3076
+ * @param {string} [pContainerID] - The container element ID (defaults to 'Pict-Content-Body')
3077
+ */showLoading(pMessage,pContainerID){let tmpContainerID=pContainerID||'Pict-Content-Body';let tmpMessage=pMessage||'Loading content...';this.pict.ContentAssignment.assignContent('#'+tmpContainerID,'<div class="pict-content-loading">'+tmpMessage+'</div>');}}module.exports=PictContentView;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],68:[function(require,module,exports){/**
3076
3078
  * Pict-MDE-CodeMirror: Helper module for PictSectionMarkdownEditor
3077
3079
  *
3078
3080
  * Handles CodeMirror editor instance creation, extension configuration,
@@ -3088,22 +3090,53 @@ if(!this.pict.AppData.PictFileBrowser.FileList){this.pict.AppData.PictFileBrowse
3088
3090
  * @param {number} pSegmentIndex - The segment index
3089
3091
  * @param {string} pContent - The initial content
3090
3092
  */pView._createEditorInContainer=function _createEditorInContainer(pContainer,pSegmentIndex,pContent){let tmpCM=pView._codeMirrorModules;// Build the extensions array
3091
- let tmpExtensions=[];// Add consumer-provided extensions (e.g. basicSetup, markdown())
3093
+ let tmpExtensions=[];// Keyboard shortcuts for formatting, inter-segment navigation, and image paste handling
3094
+ // IMPORTANT: Must be added BEFORE consumer extensions (e.g. basicSetup) so that
3095
+ // our domEventHandlers fire before CM6's internal keymap handlers.
3096
+ tmpExtensions.push(tmpCM.EditorView.domEventHandlers({keydown:(pEvent,pEditorView)=>{// Ctrl/Cmd + B = bold
3097
+ if((pEvent.ctrlKey||pEvent.metaKey)&&pEvent.key==='b'){pEvent.preventDefault();pView.applyFormatting(pSegmentIndex,'bold');return true;}// Ctrl/Cmd + I = italic
3098
+ if((pEvent.ctrlKey||pEvent.metaKey)&&pEvent.key==='i'){pEvent.preventDefault();pView.applyFormatting(pSegmentIndex,'italic');return true;}// Ctrl/Cmd + E = inline code
3099
+ if((pEvent.ctrlKey||pEvent.metaKey)&&pEvent.key==='e'){pEvent.preventDefault();pView.applyFormatting(pSegmentIndex,'code');return true;}},paste:(pEvent,pEditorView)=>{// Check clipboard for image data
3100
+ let tmpItems=pEvent.clipboardData&&pEvent.clipboardData.items;if(!tmpItems){return false;}for(let i=0;i<tmpItems.length;i++){if(tmpItems[i].type.startsWith('image/')){pEvent.preventDefault();let tmpFile=tmpItems[i].getAsFile();if(tmpFile){pView._processImageFile(tmpFile,pSegmentIndex);}return true;}}return false;},drop:(pEvent,pEditorView)=>{// Intercept image file drops at the CodeMirror level to prevent
3101
+ // the browser from inserting the image as a raw DOM element.
3102
+ // Without this, both CodeMirror's default drop behavior AND the
3103
+ // container-level handler fire, causing rendering artifacts.
3104
+ if(pView._dragSourceIndex>=0){return false;// segment-reorder drag, not a file drop
3105
+ }if(!pEvent.dataTransfer||!pEvent.dataTransfer.files||pEvent.dataTransfer.files.length<1){return false;}let tmpFile=pEvent.dataTransfer.files[0];if(tmpFile.type&&tmpFile.type.startsWith('image/')){pEvent.preventDefault();pEvent.stopPropagation();pView._processImageFile(tmpFile,pSegmentIndex);// Clean up the dragover visual indicator on the container
3106
+ let tmpContainer=pEditorView.dom.closest('.pict-mde-segment-editor');if(tmpContainer){tmpContainer.classList.remove('pict-mde-image-dragover');}return true;}return false;}}));// Add consumer-provided extensions (e.g. basicSetup, markdown())
3092
3107
  if(tmpCM.extensions&&Array.isArray(tmpCM.extensions)){tmpExtensions=tmpExtensions.concat(tmpCM.extensions);}// Update listener for content changes, focus, and cursor tracking
3093
3108
  tmpExtensions.push(tmpCM.EditorView.updateListener.of(pUpdate=>{if(pUpdate.docChanged){pView._onSegmentContentChange(pSegmentIndex,pUpdate.state.doc.toString());}// Track focus changes to toggle the active class
3094
3109
  if(pUpdate.focusChanged){if(pUpdate.view.hasFocus){pView._setActiveSegment(pSegmentIndex);// Position sidebar at cursor on focus
3095
3110
  pView._updateSidebarPosition(pSegmentIndex);}else{// Small delay so clicking a sidebar button doesn't immediately deactivate
3096
3111
  setTimeout(()=>{if(pView._activeSegmentIndex===pSegmentIndex){// Check if focus moved to another segment or truly left
3097
3112
  let tmpSegEl=document.getElementById(`PictMDE-Segment-${pSegmentIndex}`);if(tmpSegEl&&!tmpSegEl.contains(document.activeElement)){pView._clearActiveSegment(pSegmentIndex);pView._resetSidebarPosition(pSegmentIndex);}}},100);}}// Track cursor/selection changes to move the sidebar
3098
- if(pUpdate.selectionSet&&pUpdate.view.hasFocus){pView._updateSidebarPosition(pSegmentIndex);}}));// Keyboard shortcuts for formatting and image paste handling
3099
- tmpExtensions.push(tmpCM.EditorView.domEventHandlers({keydown:(pEvent,pEditorView)=>{// Ctrl/Cmd + B = bold
3100
- if((pEvent.ctrlKey||pEvent.metaKey)&&pEvent.key==='b'){pEvent.preventDefault();pView.applyFormatting(pSegmentIndex,'bold');return true;}// Ctrl/Cmd + I = italic
3101
- if((pEvent.ctrlKey||pEvent.metaKey)&&pEvent.key==='i'){pEvent.preventDefault();pView.applyFormatting(pSegmentIndex,'italic');return true;}// Ctrl/Cmd + E = inline code
3102
- if((pEvent.ctrlKey||pEvent.metaKey)&&pEvent.key==='e'){pEvent.preventDefault();pView.applyFormatting(pSegmentIndex,'code');return true;}},paste:(pEvent,pEditorView)=>{// Check clipboard for image data
3103
- let tmpItems=pEvent.clipboardData&&pEvent.clipboardData.items;if(!tmpItems){return false;}for(let i=0;i<tmpItems.length;i++){if(tmpItems[i].type.startsWith('image/')){pEvent.preventDefault();let tmpFile=tmpItems[i].getAsFile();if(tmpFile){pView._processImageFile(tmpFile,pSegmentIndex);}return true;}}return false;}}));// Collapse long data URIs in image markdown so the editor is readable
3113
+ if(pUpdate.selectionSet&&pUpdate.view.hasFocus){pView._updateSidebarPosition(pSegmentIndex);}}));// Collapse long data URIs in image markdown so the editor is readable
3104
3114
  let tmpCollapseExtension=pView._buildDataURICollapseExtension();if(tmpCollapseExtension){tmpExtensions.push(tmpCollapseExtension);}// Make read-only if configured
3105
3115
  if(pView.options.ReadOnly){tmpExtensions.push(tmpCM.EditorState.readOnly.of(true));tmpExtensions.push(tmpCM.EditorView.editable.of(false));}// Allow consumer to customize extensions
3106
- tmpExtensions=pView.customConfigureExtensions(tmpExtensions,pSegmentIndex);let tmpState=tmpCM.EditorState.create({doc:pContent||'',extensions:tmpExtensions});let tmpEditorView=new tmpCM.EditorView({state:tmpState,parent:pContainer});pView._segmentEditors[pSegmentIndex]=tmpEditorView;};/**
3116
+ tmpExtensions=pView.customConfigureExtensions(tmpExtensions,pSegmentIndex);let tmpState=tmpCM.EditorState.create({doc:pContent||'',extensions:tmpExtensions});let tmpEditorView=new tmpCM.EditorView({state:tmpState,parent:pContainer});pView._segmentEditors[pSegmentIndex]=tmpEditorView;// -- Inter-segment arrow-key navigation --
3117
+ // Use a capture-phase DOM listener so we intercept ArrowDown/ArrowUp
3118
+ // BEFORE CodeMirror's internal keymap handlers process them.
3119
+ tmpEditorView.contentDOM.addEventListener('keydown',function(pEvent){if(pEvent.key!=='ArrowDown'&&pEvent.key!=='ArrowUp'){return;}// Don't interfere if a modifier key is held (selection, etc.)
3120
+ if(pEvent.shiftKey||pEvent.ctrlKey||pEvent.metaKey||pEvent.altKey){return;}let tmpState=tmpEditorView.state;let tmpCursorPos=tmpState.selection.main.head;let tmpLine=tmpState.doc.lineAt(tmpCursorPos);let tmpColumnOffset=tmpCursorPos-tmpLine.from;if(pEvent.key==='ArrowDown'){// Only navigate when cursor is on the very last line
3121
+ if(tmpLine.to<tmpState.doc.length){return;// not on last line — let CM handle it
3122
+ }// Find next segment
3123
+ let tmpOrderedIndices=pView._getOrderedSegmentIndices();let tmpLogicalIndex=pView._getLogicalIndex(pSegmentIndex);if(tmpLogicalIndex<0||tmpLogicalIndex>=tmpOrderedIndices.length-1){return;// last segment — nowhere to go
3124
+ }let tmpNextInternalIndex=tmpOrderedIndices[tmpLogicalIndex+1];let tmpNextEditor=pView._segmentEditors[tmpNextInternalIndex];if(!tmpNextEditor){return;}pEvent.preventDefault();pEvent.stopPropagation();// Focus the next editor and place cursor on the first line
3125
+ let tmpFirstLine=tmpNextEditor.state.doc.line(1);let tmpTargetCol=Math.min(tmpColumnOffset,tmpFirstLine.to-tmpFirstLine.from);tmpNextEditor.focus();tmpNextEditor.dispatch({selection:{anchor:tmpFirstLine.from+tmpTargetCol}});pView._setActiveSegment(tmpNextInternalIndex);}else if(pEvent.key==='ArrowUp'){// Only navigate when cursor is on the very first line
3126
+ if(tmpLine.number>1){return;// not on first line — let CM handle it
3127
+ }// Find previous segment
3128
+ let tmpOrderedIndices=pView._getOrderedSegmentIndices();let tmpLogicalIndex=pView._getLogicalIndex(pSegmentIndex);if(tmpLogicalIndex<=0){return;// first segment — nowhere to go
3129
+ }let tmpPrevInternalIndex=tmpOrderedIndices[tmpLogicalIndex-1];let tmpPrevEditor=pView._segmentEditors[tmpPrevInternalIndex];if(!tmpPrevEditor){return;}pEvent.preventDefault();pEvent.stopPropagation();// Focus the previous editor and place cursor on the last line
3130
+ let tmpLastLine=tmpPrevEditor.state.doc.line(tmpPrevEditor.state.doc.lines);let tmpTargetCol=Math.min(tmpColumnOffset,tmpLastLine.to-tmpLastLine.from);tmpPrevEditor.focus();tmpPrevEditor.dispatch({selection:{anchor:tmpLastLine.from+tmpTargetCol}});pView._setActiveSegment(tmpPrevInternalIndex);}},true);// <-- capture phase
3131
+ // -- Capture-phase drop listener for image files --
3132
+ // Safari processes native contenteditable drops before CodeMirror's
3133
+ // bubble-phase domEventHandlers fire, which can insert a raw <img>
3134
+ // element into the editor DOM. A capture-phase listener fires first
3135
+ // and lets us preventDefault() before the browser acts.
3136
+ tmpEditorView.contentDOM.addEventListener('drop',function(pEvent){if(pView._dragSourceIndex>=0){return;// segment-reorder drag
3137
+ }if(!pEvent.dataTransfer||!pEvent.dataTransfer.files||pEvent.dataTransfer.files.length<1){return;}let tmpFile=pEvent.dataTransfer.files[0];if(tmpFile.type&&tmpFile.type.startsWith('image/')){pEvent.preventDefault();pEvent.stopPropagation();pView._processImageFile(tmpFile,pSegmentIndex);// Clean up the dragover visual indicator
3138
+ let tmpEditorEl=document.getElementById(`PictMDE-SegmentEditor-${pSegmentIndex}`);if(tmpEditorEl){tmpEditorEl.classList.remove('pict-mde-image-dragover');}}},true);// <-- capture phase
3139
+ };/**
3107
3140
  * Build a CodeMirror extension that visually collapses long data URIs
3108
3141
  * inside markdown image syntax.
3109
3142
  *
@@ -3386,7 +3419,7 @@ pView._renderedViewGeneration++;let tmpGeneration=pView._renderedViewGeneration;
3386
3419
  let tmpContentContainer=tmpContainer.querySelector(`#${tmpRenderedViewID} .pict-content`);if(tmpContentContainer){let tmpContentID='PictMDE-RenderedViewContent';tmpContentContainer.id=tmpContentID;pView._postRenderMermaid(tmpContentID,-1,tmpGeneration);pView._postRenderKaTeX(tmpContentID);}};/**
3387
3420
  * Switch back from rendered view to the editing view: rebuild the
3388
3421
  * full editor UI from the data.
3389
- */pView._restoreEditingView=function _restoreEditingView(){let tmpContainer=pView._getContainerElement();if(!tmpContainer){return;}tmpContainer.classList.remove('pict-mde-rendered-mode');pView._buildEditorUI();};};},{"pict-section-content":50}],73:[function(require,module,exports){module.exports={"DefaultRenderable":"MarkdownEditor-Wrap","DefaultDestinationAddress":"#MarkdownEditor-Container-Div","Templates":[{"Hash":"MarkdownEditor-Container","Template":/*html*/`<div class="pict-mde" id="PictMDE-Container"></div>`},{"Hash":"MarkdownEditor-Segment","Template":/*html*/`<div class="pict-mde-segment" id="PictMDE-Segment-{~D:Record.SegmentIndex~}" data-segment-index="{~D:Record.SegmentIndex~}">
3422
+ */pView._restoreEditingView=function _restoreEditingView(){let tmpContainer=pView._getContainerElement();if(!tmpContainer){return;}tmpContainer.classList.remove('pict-mde-rendered-mode');pView._buildEditorUI();};};},{"pict-section-content":65}],73:[function(require,module,exports){module.exports={"DefaultRenderable":"MarkdownEditor-Wrap","DefaultDestinationAddress":"#MarkdownEditor-Container-Div","Templates":[{"Hash":"MarkdownEditor-Container","Template":/*html*/`<div class="pict-mde" id="PictMDE-Container"></div>`},{"Hash":"MarkdownEditor-Segment","Template":/*html*/`<div class="pict-mde-segment" id="PictMDE-Segment-{~D:Record.SegmentIndex~}" data-segment-index="{~D:Record.SegmentIndex~}">
3390
3423
  <div class="pict-mde-left-controls">
3391
3424
  <div class="pict-mde-quadrant-tl"></div>
3392
3425
  <div class="pict-mde-quadrant-bl"></div>
@@ -3554,8 +3587,9 @@ let tmpContentContainer=tmpContainer.querySelector(`#${tmpRenderedViewID} .pict-
3554
3587
  /* ---- Editor body (middle column) ---- */
3555
3588
  .pict-mde-segment-body
3556
3589
  {
3557
- flex: 1 1 auto;
3590
+ flex: 1 1 0%;
3558
3591
  min-width: 0;
3592
+ overflow: hidden;
3559
3593
  background: #FFFFFF;
3560
3594
  transition: background-color 0.15s ease;
3561
3595
  }
@@ -3613,6 +3647,13 @@ let tmpContentContainer=tmpContainer.querySelector(`#${tmpRenderedViewID} .pict-
3613
3647
  display: block;
3614
3648
  border-top: 1px solid #EDEDED;
3615
3649
  background: #FCFCFC;
3650
+ overflow: hidden;
3651
+ }
3652
+ /* Constrain images in the rich preview even if pict-section-content CSS loads late */
3653
+ .pict-mde-rich-preview img
3654
+ {
3655
+ max-width: 100%;
3656
+ height: auto;
3616
3657
  }
3617
3658
  /* Global preview toggle: hide all previews when container has class */
3618
3659
  .pict-mde.pict-mde-previews-hidden .pict-mde-rich-preview.pict-mde-has-rich-preview,
@@ -3885,14 +3926,190 @@ let tmpContentContainer=tmpContainer.querySelector(`#${tmpRenderedViewID} .pict-
3885
3926
  white-space: nowrap;
3886
3927
  }
3887
3928
 
3888
- /* ---- Line number / controls toggle: gutters hidden by default ---- */
3889
- .pict-mde .cm-editor .cm-gutters
3890
- {
3891
- display: none;
3929
+ /* ---- Line number / controls toggle: gutters hidden by default ---- */
3930
+ .pict-mde .cm-editor .cm-gutters
3931
+ {
3932
+ display: none;
3933
+ }
3934
+ .pict-mde.pict-mde-controls-on .cm-editor .cm-gutters
3935
+ {
3936
+ display: flex;
3937
+ }
3938
+
3939
+ /* ============================================
3940
+ RESPONSIVE: Tablet / Phone (max-width: 768px)
3941
+ ============================================ */
3942
+ @media (max-width: 768px)
3943
+ {
3944
+ /* Prevent any horizontal overflow in the editor */
3945
+ .pict-mde
3946
+ {
3947
+ overflow-x: hidden;
3948
+ max-width: 100%;
3949
+ }
3950
+
3951
+ /* Reduce the left controls column width */
3952
+ .pict-mde-left-controls
3953
+ {
3954
+ flex: 0 0 16px;
3955
+ }
3956
+ .pict-mde-left-btn
3957
+ {
3958
+ width: 16px;
3959
+ height: 18px;
3960
+ font-size: 10px;
3961
+ }
3962
+
3963
+ /* Make left-side buttons always visible on touch (no hover) */
3964
+ .pict-mde-left-btn
3965
+ {
3966
+ opacity: 0.6;
3967
+ }
3968
+
3969
+ /* Narrow the drag handle */
3970
+ .pict-mde-drag-handle
3971
+ {
3972
+ flex: 0 0 5px;
3973
+ }
3974
+
3975
+ /* Shrink the right sidebar column */
3976
+ .pict-mde-sidebar
3977
+ {
3978
+ flex: 0 0 24px;
3979
+ }
3980
+ .pict-mde-sidebar-btn
3981
+ {
3982
+ width: 20px;
3983
+ height: 20px;
3984
+ font-size: 11px;
3985
+ }
3986
+
3987
+ /* Make right sidebar buttons always visible (touch devices) */
3988
+ .pict-mde-quadrant-tr,
3989
+ .pict-mde-quadrant-br
3990
+ {
3991
+ opacity: 0.7;
3992
+ }
3993
+ .pict-mde-segment.pict-mde-active .pict-mde-quadrant-tr,
3994
+ .pict-mde-segment.pict-mde-active .pict-mde-quadrant-br
3995
+ {
3996
+ opacity: 1;
3997
+ }
3998
+
3999
+ /* Reduce CodeMirror content padding */
4000
+ .pict-mde-segment-editor .cm-editor .cm-content
4001
+ {
4002
+ padding: 6px 6px;
4003
+ }
4004
+
4005
+ /* Reduce font size slightly for more content on screen */
4006
+ .pict-mde-segment-editor .cm-editor .cm-scroller
4007
+ {
4008
+ font-size: 13px;
4009
+ }
4010
+
4011
+ /* Add segment button: reduce left margin */
4012
+ .pict-mde-add-segment
4013
+ {
4014
+ padding-left: 21px;
4015
+ }
4016
+
4017
+ /* Rich preview: less padding */
4018
+ .pict-mde-rich-preview .pict-content
4019
+ {
4020
+ padding: 8px;
4021
+ font-size: 12px;
4022
+ }
4023
+
4024
+ /* Image previews: smaller max dimensions */
4025
+ .pict-mde-image-preview img
4026
+ {
4027
+ max-width: 120px;
4028
+ max-height: 100px;
4029
+ }
4030
+
4031
+ /* Rendered view: less padding */
4032
+ .pict-mde-rendered-view
4033
+ {
4034
+ padding: 10px 8px;
4035
+ }
3892
4036
  }
3893
- .pict-mde.pict-mde-controls-on .cm-editor .cm-gutters
4037
+
4038
+ /* ============================================
4039
+ RESPONSIVE: Small phone (max-width: 480px)
4040
+ ============================================ */
4041
+ @media (max-width: 480px)
3894
4042
  {
3895
- display: flex;
4043
+ /* Wrap segment so left controls flow to the top as a horizontal bar */
4044
+ .pict-mde-segment
4045
+ {
4046
+ flex-wrap: wrap;
4047
+ }
4048
+
4049
+ /* Left controls become a horizontal toolbar at the top of the segment */
4050
+ .pict-mde-left-controls
4051
+ {
4052
+ flex: 0 0 100%;
4053
+ flex-direction: row;
4054
+ justify-content: flex-start;
4055
+ gap: 2px;
4056
+ padding: 3px 4px;
4057
+ order: -1;
4058
+ background: #F5F5F5;
4059
+ border-bottom: 1px solid #EDEDED;
4060
+ }
4061
+ .pict-mde-left-btn
4062
+ {
4063
+ width: 24px;
4064
+ height: 24px;
4065
+ font-size: 12px;
4066
+ opacity: 0.7;
4067
+ }
4068
+
4069
+ /* Left quadrants flow horizontally */
4070
+ .pict-mde-quadrant-tl,
4071
+ .pict-mde-quadrant-bl
4072
+ {
4073
+ flex-direction: row;
4074
+ gap: 2px;
4075
+ position: static;
4076
+ }
4077
+
4078
+ /* Segment body: explicit basis so it fills the wrapped row */
4079
+ .pict-mde-segment-body
4080
+ {
4081
+ flex: 1 1 calc(100% - 20px);
4082
+ }
4083
+
4084
+ /* Hide drag handle on very small screens */
4085
+ .pict-mde-drag-handle
4086
+ {
4087
+ display: none;
4088
+ }
4089
+
4090
+ /* Right sidebar: further shrink */
4091
+ .pict-mde-sidebar
4092
+ {
4093
+ flex: 0 0 20px;
4094
+ }
4095
+ .pict-mde-sidebar-btn
4096
+ {
4097
+ width: 18px;
4098
+ height: 18px;
4099
+ font-size: 10px;
4100
+ }
4101
+
4102
+ /* Add segment: no left offset since controls are at top */
4103
+ .pict-mde-add-segment
4104
+ {
4105
+ padding-left: 0;
4106
+ }
4107
+
4108
+ /* Even tighter CodeMirror padding */
4109
+ .pict-mde-segment-editor .cm-editor .cm-content
4110
+ {
4111
+ padding: 4px 4px;
4112
+ }
3896
4113
  }
3897
4114
  `};},{}],74:[function(require,module,exports){const libPictViewClass=require('pict-view');const libPictSectionContent=require('pict-section-content');const _DefaultConfiguration=require('./Pict-Section-MarkdownEditor-DefaultConfiguration.js');// Helper modules
3898
4115
  const libFormatting=require('./Pict-MDE-Formatting.js');const libImageHandling=require('./Pict-MDE-ImageHandling.js');const libDragAndReorder=require('./Pict-MDE-DragAndReorder.js');const libRichPreview=require('./Pict-MDE-RichPreview.js');const libCodeMirror=require('./Pict-MDE-CodeMirror.js');class PictSectionMarkdownEditor extends libPictViewClass{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_DefaultConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);this.initialRenderComplete=false;// CodeMirror prototype references (injected by consumer or found on window)
@@ -3903,7 +4120,7 @@ this._viewCallIdentifier=false;// Currently active (focused) segment index, or -
3903
4120
  this._activeSegmentIndex=-1;// Drag state for reorder
3904
4121
  this._dragSourceIndex=-1;// Whether controls (line numbers + right sidebar) are currently visible
3905
4122
  this._controlsVisible=true;// Whether rich previews are globally visible
3906
- this._previewsVisible=false;// Set of logical segment indices where preview has been individually hidden
4123
+ this._previewsVisible=true;// Set of logical segment indices where preview has been individually hidden
3907
4124
  this._hiddenPreviewSegments={};// Debounce timers for image preview updates (keyed by segment index)
3908
4125
  this._imagePreviewTimers={};// Debounce timers for rich content preview updates (keyed by segment index)
3909
4126
  this._richPreviewTimers={};// Generation counters for mermaid async rendering (keyed by segment index)
@@ -3923,8 +4140,9 @@ libFormatting.attach(this);libImageHandling.attach(this);libDragAndReorder.attac
3923
4140
  * @param {object} [pCodeMirrorModules] - The CodeMirror modules object
3924
4141
  * @returns {boolean|void}
3925
4142
  */connectCodeMirrorModules(pCodeMirrorModules){if(pCodeMirrorModules&&typeof pCodeMirrorModules==='object'){if(typeof pCodeMirrorModules.EditorView==='function'&&typeof pCodeMirrorModules.EditorState==='function'){this._codeMirrorModules=pCodeMirrorModules;return;}}// Try to find CodeMirror modules in global scope
3926
- if(typeof window!=='undefined'){if(window.CodeMirrorModules&&typeof window.CodeMirrorModules.EditorView==='function'){this.log.trace(`PICT-MarkdownEditor Found CodeMirror modules on window.CodeMirrorModules.`);this._codeMirrorModules=window.CodeMirrorModules;return;}}this.log.error(`PICT-MarkdownEditor No CodeMirror modules found. Provide them via connectCodeMirrorModules() or set window.CodeMirrorModules.`);return false;}onAfterRender(pRenderable){// Inject CSS from all registered views
3927
- this.pict.CSSMap.injectCSS();if(!this.initialRenderComplete){this.onAfterInitialRender();this.initialRenderComplete=true;}return super.onAfterRender(pRenderable);}onAfterInitialRender(){// Resolve CodeMirror modules if not already set
4143
+ if(typeof window!=='undefined'){if(window.CodeMirrorModules&&typeof window.CodeMirrorModules.EditorView==='function'){this.log.trace(`PICT-MarkdownEditor Found CodeMirror modules on window.CodeMirrorModules.`);this._codeMirrorModules=window.CodeMirrorModules;return;}}this.log.error(`PICT-MarkdownEditor No CodeMirror modules found. Provide them via connectCodeMirrorModules() or set window.CodeMirrorModules.`);return false;}onAfterRender(pRenderable){if(!this.initialRenderComplete){this.onAfterInitialRender();this.initialRenderComplete=true;}// Inject CSS from all registered views (after onAfterInitialRender so
4144
+ // that pict-section-content's CSS is registered before injection)
4145
+ this.pict.CSSMap.injectCSS();return super.onAfterRender(pRenderable);}onAfterInitialRender(){// Resolve CodeMirror modules if not already set
3928
4146
  if(!this._codeMirrorModules){this.connectCodeMirrorModules();}if(!this._codeMirrorModules){this.log.error(`PICT-MarkdownEditor Cannot initialize; no CodeMirror modules available.`);return false;}// Register pict-section-content's CSS for rich preview rendering.
3929
4147
  // This ensures the rendered markdown (headings, code blocks, tables, etc.)
3930
4148
  // is styled correctly inside the preview area.
@@ -4037,7 +4255,8 @@ let tmpVisible=pVisible;if(typeof pSegmentIndexOrVisible==='boolean'){tmpVisible
4037
4255
  * @param {number} pSegmentIndex - The internal segment index
4038
4256
  * @param {boolean} [pVisible] - If provided, set to this value; otherwise toggle
4039
4257
  */toggleSegmentPreview(pSegmentIndex,pVisible){// Convert internal index to logical index for persistent tracking
4040
- let tmpLogicalIndex=this._getLogicalIndex(pSegmentIndex);if(tmpLogicalIndex<0){return;}let tmpCurrentlyHidden=!!this._hiddenPreviewSegments[tmpLogicalIndex];if(typeof pVisible==='boolean'){tmpCurrentlyHidden=!pVisible;}else{tmpCurrentlyHidden=!tmpCurrentlyHidden;}if(tmpCurrentlyHidden){this._hiddenPreviewSegments[tmpLogicalIndex]=true;}else{delete this._hiddenPreviewSegments[tmpLogicalIndex];}let tmpSegmentEl=document.getElementById(`PictMDE-Segment-${pSegmentIndex}`);if(tmpSegmentEl){if(tmpCurrentlyHidden){tmpSegmentEl.classList.add('pict-mde-preview-hidden');}else{tmpSegmentEl.classList.remove('pict-mde-preview-hidden');}}}// -- Segment Data Management --
4258
+ let tmpLogicalIndex=this._getLogicalIndex(pSegmentIndex);if(tmpLogicalIndex<0){return;}let tmpCurrentlyHidden=!!this._hiddenPreviewSegments[tmpLogicalIndex];if(typeof pVisible==='boolean'){tmpCurrentlyHidden=!pVisible;}else{tmpCurrentlyHidden=!tmpCurrentlyHidden;}if(tmpCurrentlyHidden){this._hiddenPreviewSegments[tmpLogicalIndex]=true;}else{delete this._hiddenPreviewSegments[tmpLogicalIndex];}let tmpSegmentEl=document.getElementById(`PictMDE-Segment-${pSegmentIndex}`);if(tmpSegmentEl){if(tmpCurrentlyHidden){tmpSegmentEl.classList.add('pict-mde-preview-hidden');}else{tmpSegmentEl.classList.remove('pict-mde-preview-hidden');// Render preview content when making it visible
4259
+ this._updateRichPreviews(pSegmentIndex);this._updateImagePreviews(pSegmentIndex);}}}// -- Segment Data Management --
4041
4260
  /**
4042
4261
  * Get the segments array from the configured data address.
4043
4262
  *
@@ -4121,7 +4340,7 @@ this._swapHiddenPreviewState(tmpLogicalIndex,tmpLogicalIndex+1);this._buildEdito
4121
4340
  */marshalFromView(){super.marshalFromView();this._marshalAllEditorsToData();}/**
4122
4341
  * Destroy all editors and clean up.
4123
4342
  */destroy(){for(let tmpIndex in this._segmentEditors){if(this._segmentEditors[tmpIndex]){this._segmentEditors[tmpIndex].destroy();}}this._segmentEditors={};// Clear rich preview debounce timers
4124
- for(let tmpIndex in this._richPreviewTimers){clearTimeout(this._richPreviewTimers[tmpIndex]);}this._richPreviewTimers={};this._richPreviewGenerations={};}}module.exports=PictSectionMarkdownEditor;module.exports.default_configuration=_DefaultConfiguration;},{"./Pict-MDE-CodeMirror.js":68,"./Pict-MDE-DragAndReorder.js":69,"./Pict-MDE-Formatting.js":70,"./Pict-MDE-ImageHandling.js":71,"./Pict-MDE-RichPreview.js":72,"./Pict-Section-MarkdownEditor-DefaultConfiguration.js":73,"pict-section-content":50,"pict-view":76}],75:[function(require,module,exports){module.exports={"name":"pict-view","version":"1.0.67","description":"Pict View Base Class","main":"source/Pict-View.js","scripts":{"test":"npx quack test","tests":"npx quack test -g","start":"node source/Pict-View.js","coverage":"npx quack coverage","build":"npx quack build","docker-dev-build":"docker build ./ -f Dockerfile_LUXURYCode -t pict-view-image:local","docker-dev-run":"docker run -it -d --name pict-view-dev -p 30001:8080 -p 38086:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/pict-view\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" pict-view-image:local","docker-dev-shell":"docker exec -it pict-view-dev /bin/bash","types":"tsc -p .","lint":"eslint source/**"},"types":"types/source/Pict-View.d.ts","repository":{"type":"git","url":"git+https://github.com/stevenvelozo/pict-view.git"},"author":"steven velozo <steven@velozo.com>","license":"MIT","bugs":{"url":"https://github.com/stevenvelozo/pict-view/issues"},"homepage":"https://github.com/stevenvelozo/pict-view#readme","devDependencies":{"@eslint/js":"^9.39.1","browser-env":"^3.3.0","eslint":"^9.39.1","pict":"^1.0.348","quackage":"^1.0.58","typescript":"^5.9.3"},"mocha":{"diff":true,"extension":["js"],"package":"./package.json","reporter":"spec","slow":"75","timeout":"5000","ui":"tdd","watch-files":["source/**/*.js","test/**/*.js"],"watch-ignore":["lib/vendor"]},"dependencies":{"fable":"^3.1.63","fable-serviceproviderbase":"^3.0.19"}};},{}],76:[function(require,module,exports){const libFableServiceBase=require('fable-serviceproviderbase');const libPackage=require('../package.json');const defaultPictViewSettings={DefaultRenderable:false,DefaultDestinationAddress:false,DefaultTemplateRecordAddress:false,ViewIdentifier:false,// If this is set to true, when the App initializes this will.
4343
+ for(let tmpIndex in this._richPreviewTimers){clearTimeout(this._richPreviewTimers[tmpIndex]);}this._richPreviewTimers={};this._richPreviewGenerations={};}}module.exports=PictSectionMarkdownEditor;module.exports.default_configuration=_DefaultConfiguration;},{"./Pict-MDE-CodeMirror.js":68,"./Pict-MDE-DragAndReorder.js":69,"./Pict-MDE-Formatting.js":70,"./Pict-MDE-ImageHandling.js":71,"./Pict-MDE-RichPreview.js":72,"./Pict-Section-MarkdownEditor-DefaultConfiguration.js":73,"pict-section-content":65,"pict-view":76}],75:[function(require,module,exports){module.exports={"name":"pict-view","version":"1.0.67","description":"Pict View Base Class","main":"source/Pict-View.js","scripts":{"test":"npx quack test","tests":"npx quack test -g","start":"node source/Pict-View.js","coverage":"npx quack coverage","build":"npx quack build","docker-dev-build":"docker build ./ -f Dockerfile_LUXURYCode -t pict-view-image:local","docker-dev-run":"docker run -it -d --name pict-view-dev -p 30001:8080 -p 38086:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/pict-view\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" pict-view-image:local","docker-dev-shell":"docker exec -it pict-view-dev /bin/bash","types":"tsc -p .","lint":"eslint source/**"},"types":"types/source/Pict-View.d.ts","repository":{"type":"git","url":"git+https://github.com/stevenvelozo/pict-view.git"},"author":"steven velozo <steven@velozo.com>","license":"MIT","bugs":{"url":"https://github.com/stevenvelozo/pict-view/issues"},"homepage":"https://github.com/stevenvelozo/pict-view#readme","devDependencies":{"@eslint/js":"^9.39.1","browser-env":"^3.3.0","eslint":"^9.39.1","pict":"^1.0.348","quackage":"^1.0.58","typescript":"^5.9.3"},"mocha":{"diff":true,"extension":["js"],"package":"./package.json","reporter":"spec","slow":"75","timeout":"5000","ui":"tdd","watch-files":["source/**/*.js","test/**/*.js"],"watch-ignore":["lib/vendor"]},"dependencies":{"fable":"^3.1.63","fable-serviceproviderbase":"^3.0.19"}};},{}],76:[function(require,module,exports){const libFableServiceBase=require('fable-serviceproviderbase');const libPackage=require('../package.json');const defaultPictViewSettings={DefaultRenderable:false,DefaultDestinationAddress:false,DefaultTemplateRecordAddress:false,ViewIdentifier:false,// If this is set to true, when the App initializes this will.
4125
4344
  // After the App initializes, initialize will be called as soon as it's added.
4126
4345
  AutoInitialize:true,AutoInitializeOrdinal:0,// If this is set to true, when the App autorenders (on load) this will.
4127
4346
  // After the App initializes, render will be called as soon as it's added.
@@ -5795,93 +6014,7 @@ let tmpBrand='Documentation';if(tmpDocuserve.CoverLoaded&&tmpDocuserve.Cover&&tm
5795
6014
  * Sanitize a title string, preserving only <small> tags.
5796
6015
  */sanitizeTitle(pText){if(!pText){return'';}return this.escapeHTML(pText).replace(/&lt;small&gt;/gi,'<small>').replace(/&lt;\/small&gt;/gi,'</small>');}/**
5797
6016
  * Escape HTML special characters.
5798
- */escapeHTML(pText){if(!pText){return'';}return pText.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}}module.exports=DocuserveTopBarView;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],93:[function(require,module,exports){arguments[4][50][0].apply(exports,arguments);},{"./providers/Pict-Provider-Content.js":94,"./views/Pict-View-Content.js":95,"dup":50}],94:[function(require,module,exports){const libPictProvider=require('pict-provider');const libCreateHighlighter=require('pict-section-code').createHighlighter;/**
5799
- * Content Provider for Pict Section Content
5800
- *
5801
- * A general-purpose markdown-to-HTML parser with support for:
5802
- * - Headings, paragraphs, lists, blockquotes, horizontal rules
5803
- * - Fenced code blocks with language tags (nested fence support)
5804
- * - Syntax highlighting and line numbers for code blocks (via pict-section-code)
5805
- * - Tables (GFM pipe syntax)
5806
- * - Mermaid diagram blocks
5807
- * - KaTeX math (inline and display)
5808
- * - Bold, italic, inline code, links, images
5809
- *
5810
- * Link resolution is customizable via an optional callback.
5811
- */class PictContentProvider extends libPictProvider{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);}/**
5812
- * Highlight a code string using pict-section-code's syntax highlighter.
5813
- * Uses a mock element to interface with the highlighter's DOM-based API.
5814
- *
5815
- * @param {string} pCode - The raw code string
5816
- * @param {string} pLanguage - The language identifier (e.g. "javascript", "html")
5817
- * @returns {string} The syntax-highlighted HTML
5818
- */highlightCode(pCode,pLanguage){if(!pCode){return'';}let tmpHighlighter=libCreateHighlighter(pLanguage);// Create a mock element to interface with the highlighter
5819
- let tmpMockElement={textContent:pCode,innerHTML:''};tmpHighlighter(tmpMockElement);return tmpMockElement.innerHTML;}/**
5820
- * Generate line number HTML for a code block.
5821
- *
5822
- * @param {string} pCode - The raw code string
5823
- * @returns {string} HTML string with line number spans
5824
- */generateLineNumbers(pCode){if(!pCode){return'<span>1</span>';}let tmpLineCount=pCode.split('\n').length;let tmpHTML='';for(let i=1;i<=tmpLineCount;i++){tmpHTML+='<span>'+i+'</span>';}return tmpHTML;}/**
5825
- * Parse a markdown string into HTML.
5826
- *
5827
- * @param {string} pMarkdown - The raw markdown text
5828
- * @param {Function} [pLinkResolver] - Optional callback for link resolution: (pHref, pLinkText) => { href, target, rel } or null
5829
- * @param {Function} [pImageResolver] - Optional callback for image URL resolution: (pSrc, pAlt) => resolvedSrc or null
5830
- * @returns {string} The parsed HTML
5831
- */parseMarkdown(pMarkdown,pLinkResolver,pImageResolver){if(!pMarkdown){return'';}let tmpLines=pMarkdown.split('\n');let tmpHTML=[];let tmpInCodeBlock=false;let tmpCodeFenceLength=0;let tmpCodeLang='';let tmpCodeLines=[];let tmpInList=false;let tmpListType='';let tmpInBlockquote=false;let tmpBlockquoteLines=[];let tmpInMathBlock=false;let tmpMathLines=[];let tmpParagraphLines=[];// Helper to flush accumulated paragraph lines into a single <p> tag
5832
- let fFlushParagraph=()=>{if(tmpParagraphLines.length>0){tmpHTML.push('<p>'+tmpParagraphLines.map(pLine=>{return this.parseInline(pLine,pLinkResolver,pImageResolver);}).join(' ')+'</p>');tmpParagraphLines=[];}};for(let i=0;i<tmpLines.length;i++){let tmpLine=tmpLines[i];// Display math blocks ($$...$$) — skip if inside a code block
5833
- if(!tmpInCodeBlock&&tmpLine.trim().match(/^\$\$/)){if(tmpInMathBlock){// End math block
5834
- tmpHTML.push('<div class="pict-content-katex-display">'+tmpMathLines.join('\n')+'</div>');tmpInMathBlock=false;tmpMathLines=[];}else{// Flush any pending paragraph
5835
- fFlushParagraph();// Close any open list or blockquote
5836
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver,pImageResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}tmpInMathBlock=true;}continue;}if(tmpInMathBlock){tmpMathLines.push(tmpLine);continue;}// Code blocks (fenced) — track fence length so ````x```` nests around ```y```
5837
- let tmpFenceMatch=tmpLine.match(/^(`{3,})/);if(tmpFenceMatch){let tmpFenceLen=tmpFenceMatch[1].length;if(tmpInCodeBlock){// Only close if the closing fence is at least as long as the opening
5838
- if(tmpFenceLen>=tmpCodeFenceLength&&tmpLine.trim()===tmpFenceMatch[1]){// End code block
5839
- if(tmpCodeLang==='mermaid'){// Mermaid diagrams: output raw content for client-side rendering
5840
- tmpHTML.push('<pre class="mermaid">'+tmpCodeLines.join('\n')+'</pre>');}else{let tmpCodeText=tmpCodeLines.join('\n');let tmpHighlightedCode=this.highlightCode(tmpCodeText,tmpCodeLang);let tmpLineNumbersHTML=this.generateLineNumbers(tmpCodeText);tmpHTML.push('<div class="pict-content-code-wrap"><div class="pict-content-code-line-numbers">'+tmpLineNumbersHTML+'</div><pre><code class="language-'+this.escapeHTML(tmpCodeLang)+'">'+tmpHighlightedCode+'</code></pre></div>');}tmpInCodeBlock=false;tmpCodeFenceLength=0;tmpCodeLang='';tmpCodeLines=[];continue;}else{// Inner fence with fewer backticks — treat as content
5841
- tmpCodeLines.push(tmpLine);continue;}}else{// Flush any pending paragraph
5842
- fFlushParagraph();// Close any open list or blockquote
5843
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver,pImageResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}// Start code block — record fence length
5844
- tmpCodeFenceLength=tmpFenceLen;tmpCodeLang=tmpLine.replace(/^`{3,}/,'').trim();tmpInCodeBlock=true;continue;}}if(tmpInCodeBlock){tmpCodeLines.push(tmpLine);continue;}// Blockquotes
5845
- if(tmpLine.match(/^>\s?/)){if(!tmpInBlockquote){// Flush any pending paragraph
5846
- fFlushParagraph();// Close any open list
5847
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}tmpInBlockquote=true;tmpBlockquoteLines=[];}tmpBlockquoteLines.push(tmpLine.replace(/^>\s?/,''));continue;}else if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver,pImageResolver)+'</blockquote>');tmpInBlockquote=false;tmpBlockquoteLines=[];}// Horizontal rule
5848
- if(tmpLine.match(/^(-{3,}|\*{3,}|_{3,})\s*$/)){fFlushParagraph();if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}tmpHTML.push('<hr>');continue;}// Headings
5849
- let tmpHeadingMatch=tmpLine.match(/^(#{1,6})\s+(.+)/);if(tmpHeadingMatch){fFlushParagraph();if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}let tmpLevel=tmpHeadingMatch[1].length;let tmpText=this.parseInline(tmpHeadingMatch[2],pLinkResolver,pImageResolver);let tmpID=tmpHeadingMatch[2].toLowerCase().replace(/[^\w\s-]/g,'').replace(/\s+/g,'-');tmpHTML.push('<h'+tmpLevel+' id="'+tmpID+'">'+tmpText+'</h'+tmpLevel+'>');continue;}// Unordered list items
5850
- let tmpULMatch=tmpLine.match(/^(\s*)[-*+]\s+(.*)/);if(tmpULMatch){fFlushParagraph();if(!tmpInList||tmpListType!=='ul'){if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}tmpHTML.push('<ul>');tmpInList=true;tmpListType='ul';}tmpHTML.push('<li>'+this.parseInline(tmpULMatch[2],pLinkResolver,pImageResolver)+'</li>');continue;}// Ordered list items
5851
- let tmpOLMatch=tmpLine.match(/^(\s*)\d+\.\s+(.*)/);if(tmpOLMatch){fFlushParagraph();if(!tmpInList||tmpListType!=='ol'){if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}tmpHTML.push('<ol>');tmpInList=true;tmpListType='ol';}tmpHTML.push('<li>'+this.parseInline(tmpOLMatch[2],pLinkResolver,pImageResolver)+'</li>');continue;}// Close list if we've left list items
5852
- if(tmpInList&&tmpLine.trim()!==''){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}// Empty line — flush any accumulated paragraph
5853
- if(tmpLine.trim()===''){fFlushParagraph();continue;}// Table detection
5854
- if(tmpLine.match(/^\|/)&&i+1<tmpLines.length&&tmpLines[i+1].match(/^\|[\s-:|]+\|/)){fFlushParagraph();// Close any open list
5855
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');tmpInList=false;}let tmpTableHTML='<table>';// Header row
5856
- let tmpHeaders=tmpLine.split('|').filter(pCell=>{return pCell.trim()!=='';});tmpTableHTML+='<thead><tr>';for(let h=0;h<tmpHeaders.length;h++){tmpTableHTML+='<th>'+this.parseInline(tmpHeaders[h].trim(),pLinkResolver,pImageResolver)+'</th>';}tmpTableHTML+='</tr></thead>';// Skip separator row
5857
- i++;// Body rows
5858
- tmpTableHTML+='<tbody>';while(i+1<tmpLines.length&&tmpLines[i+1].match(/^\|/)){i++;let tmpCells=tmpLines[i].split('|').filter(pCell=>{return pCell.trim()!=='';});tmpTableHTML+='<tr>';for(let c=0;c<tmpCells.length;c++){tmpTableHTML+='<td>'+this.parseInline(tmpCells[c].trim(),pLinkResolver,pImageResolver)+'</td>';}tmpTableHTML+='</tr>';}tmpTableHTML+='</tbody></table>';tmpHTML.push(tmpTableHTML);continue;}// Accumulate paragraph lines — consecutive non-blank text lines
5859
- // will be joined into a single <p> tag when flushed
5860
- tmpParagraphLines.push(tmpLine);}// Flush any remaining accumulated paragraph
5861
- fFlushParagraph();// Close any trailing open elements
5862
- if(tmpInList){tmpHTML.push(tmpListType==='ul'?'</ul>':'</ol>');}if(tmpInBlockquote){tmpHTML.push('<blockquote>'+this.parseMarkdown(tmpBlockquoteLines.join('\n'),pLinkResolver,pImageResolver)+'</blockquote>');}if(tmpInCodeBlock){let tmpCodeText=tmpCodeLines.join('\n');let tmpHighlightedCode=this.highlightCode(tmpCodeText,tmpCodeLang);let tmpLineNumbersHTML=this.generateLineNumbers(tmpCodeText);tmpHTML.push('<div class="pict-content-code-wrap"><div class="pict-content-code-line-numbers">'+tmpLineNumbersHTML+'</div><pre><code>'+tmpHighlightedCode+'</code></pre></div>');}return tmpHTML.join('\n');}/**
5863
- * Parse inline markdown elements (bold, italic, code, links, images, KaTeX).
5864
- *
5865
- * @param {string} pText - The text to parse
5866
- * @param {Function} [pLinkResolver] - Optional callback: (pHref, pLinkText) => { href, target, rel } or null
5867
- * @param {Function} [pImageResolver] - Optional callback: (pSrc, pAlt) => resolvedSrc or null
5868
- * @returns {string} HTML with inline elements
5869
- */parseInline(pText,pLinkResolver,pImageResolver){if(!pText){return'';}let tmpResult=pText;// Extract inline code spans into placeholders so bold/italic regexes don't mangle their contents
5870
- let tmpCodeSpans=[];tmpResult=tmpResult.replace(/`([^`]+)`/g,(pMatch,pCode)=>{let tmpIndex=tmpCodeSpans.length;tmpCodeSpans.push('<code>'+pCode+'</code>');return'\x00CODEINLINE'+tmpIndex+'\x00';});// Inline LaTeX equations ($...$) — must be processed before other inline patterns
5871
- // Match single $ delimiters that aren't adjacent to spaces (to avoid false positives with currency)
5872
- tmpResult=tmpResult.replace(/\$([^\$\s][^\$]*?[^\$\s])\$/g,'<span class="pict-content-katex-inline">$1</span>');// Also match single-character inline math like $x$
5873
- tmpResult=tmpResult.replace(/\$([^\$\s])\$/g,'<span class="pict-content-katex-inline">$1</span>');// Images
5874
- tmpResult=tmpResult.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(pMatch,pAlt,pSrc)=>{let tmpSrc=pSrc;if(typeof pImageResolver==='function'){let tmpResolved=pImageResolver(pSrc,pAlt);if(tmpResolved){tmpSrc=tmpResolved;}}return'<img src="'+tmpSrc+'" alt="'+pAlt+'">';});// Links
5875
- tmpResult=tmpResult.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(pMatch,pLinkText,pHref)=>{if(typeof pLinkResolver==='function'){let tmpResolved=pLinkResolver(pHref,pLinkText);if(tmpResolved){let tmpTarget=tmpResolved.target?' target="'+tmpResolved.target+'"':'';let tmpRel=tmpResolved.rel?' rel="'+tmpResolved.rel+'"':'';return'<a href="'+tmpResolved.href+'"'+tmpTarget+tmpRel+'>'+pLinkText+'</a>';}}// Default behavior: external links open in new tab
5876
- if(pHref.match(/^https?:\/\//)){return'<a href="'+pHref+'" target="_blank" rel="noopener">'+pLinkText+'</a>';}return'<a href="'+pHref+'">'+pLinkText+'</a>';});// Bold
5877
- tmpResult=tmpResult.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');tmpResult=tmpResult.replace(/__([^_]+)__/g,'<strong>$1</strong>');// Italic
5878
- tmpResult=tmpResult.replace(/\*([^*]+)\*/g,'<em>$1</em>');tmpResult=tmpResult.replace(/_([^_]+)_/g,'<em>$1</em>');// Restore inline code spans from placeholders
5879
- tmpResult=tmpResult.replace(/\x00CODEINLINE(\d+)\x00/g,(pMatch,pIndex)=>{return tmpCodeSpans[parseInt(pIndex)];});return tmpResult;}/**
5880
- * Escape HTML special characters.
5881
- *
5882
- * @param {string} pText - The text to escape
5883
- * @returns {string} The escaped text
5884
- */escapeHTML(pText){if(!pText){return'';}return pText.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}}module.exports=PictContentProvider;module.exports.default_configuration={ProviderIdentifier:"Pict-Content",AutoInitialize:true,AutoInitializeOrdinal:0};},{"pict-provider":46,"pict-section-code":49}],95:[function(require,module,exports){arguments[4][52][0].apply(exports,arguments);},{"dup":52,"pict-view":76}],96:[function(require,module,exports){module.exports={"Name":"Retold Content Editor","Hash":"ContentEditor","MainViewportViewIdentifier":"ContentEditor-Layout","AutoSolveAfterInitialize":true,"AutoRenderMainViewportViewAfterInitialize":false,"AutoRenderViewsAfterInitialize":false,"pict_configuration":{"Product":"ContentEditor-Pict-Application"}};},{}],97:[function(require,module,exports){const libPictApplication=require('pict-application');// File browser
6017
+ */escapeHTML(pText){if(!pText){return'';}return pText.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}}module.exports=DocuserveTopBarView;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],93:[function(require,module,exports){arguments[4][65][0].apply(exports,arguments);},{"./providers/Pict-Provider-Content.js":94,"./views/Pict-View-Content.js":95,"dup":65}],94:[function(require,module,exports){arguments[4][66][0].apply(exports,arguments);},{"dup":66,"pict-provider":46,"pict-section-code":49}],95:[function(require,module,exports){arguments[4][67][0].apply(exports,arguments);},{"dup":67,"pict-view":76}],96:[function(require,module,exports){module.exports={"Name":"Retold Content Editor","Hash":"ContentEditor","MainViewportViewIdentifier":"ContentEditor-Layout","AutoSolveAfterInitialize":true,"AutoRenderMainViewportViewAfterInitialize":false,"AutoRenderViewsAfterInitialize":false,"pict_configuration":{"Product":"ContentEditor-Pict-Application"}};},{}],97:[function(require,module,exports){const libPictApplication=require('pict-application');// File browser
5885
6018
  const libPictSectionFileBrowser=require('pict-section-filebrowser');// Provider
5886
6019
  const libContentEditorProvider=require('./providers/Pict-Provider-ContentEditor.js');// Views
5887
6020
  const libViewLayout=require('./views/PictView-Editor-Layout.js');const libViewTopBar=require('./views/PictView-Editor-TopBar.js');const libViewMarkdownEditor=require('./views/PictView-Editor-MarkdownEditor.js');const libViewCodeEditor=require('./views/PictView-Editor-CodeEditor.js');const libViewSettingsPanel=require('./views/PictView-Editor-SettingsPanel.js');const libViewMarkdownReference=require('./views/PictView-Editor-MarkdownReference.js');const libViewTopics=require('./views/PictView-Editor-Topics.js');/**
@@ -5893,13 +6026,37 @@ const libViewLayout=require('./views/PictView-Editor-Layout.js');const libViewTo
5893
6026
  */class ContentEditorApplication extends libPictApplication{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);// Register the content editor provider
5894
6027
  this.pict.addProvider('ContentEditor-Provider',libContentEditorProvider.default_configuration,libContentEditorProvider);// Register views
5895
6028
  this.pict.addView('ContentEditor-Layout',libViewLayout.default_configuration,libViewLayout);this.pict.addView('ContentEditor-TopBar',libViewTopBar.default_configuration,libViewTopBar);this.pict.addView('ContentEditor-MarkdownEditor',libViewMarkdownEditor.default_configuration,libViewMarkdownEditor);this.pict.addView('ContentEditor-CodeEditor',libViewCodeEditor.default_configuration,libViewCodeEditor);this.pict.addView('ContentEditor-SettingsPanel',libViewSettingsPanel.default_configuration,libViewSettingsPanel);this.pict.addView('ContentEditor-MarkdownReference',libViewMarkdownReference.default_configuration,libViewMarkdownReference);this.pict.addView('ContentEditor-Topics',libViewTopics.default_configuration,libViewTopics);// Register the file browser -- override destination and layout for sidebar use
5896
- let tmpFileBrowserConfig=JSON.parse(JSON.stringify(libPictSectionFileBrowser.default_configuration));tmpFileBrowserConfig.DefaultDestinationAddress='#ContentEditor-Sidebar-Container';tmpFileBrowserConfig.DefaultState.Layout='list-only';this.pict.addView('Pict-FileBrowser',tmpFileBrowserConfig,libPictSectionFileBrowser);// Register the list detail sub-view for the file list pane
5897
- this.pict.addView('Pict-FileBrowser-ListDetail',libPictSectionFileBrowser.PictViewListDetail.default_configuration,libPictSectionFileBrowser.PictViewListDetail);}onAfterInitializeAsync(fCallback){// Expose the pict instance as window.pict for inline onclick handlers
6029
+ let tmpFileBrowserConfig=JSON.parse(JSON.stringify(libPictSectionFileBrowser.default_configuration));tmpFileBrowserConfig.DefaultDestinationAddress='#ContentEditor-Sidebar-Container';tmpFileBrowserConfig.DefaultState.Layout='list-only';this.pict.addView('Pict-FileBrowser',tmpFileBrowserConfig,libPictSectionFileBrowser);// Register the list detail sub-view for the file list pane.
6030
+ // Override templates to:
6031
+ // - Add a "create folder" button to the breadcrumb bar
6032
+ // - Add a hover-visible "+" insert button on each file row
6033
+ let tmpListDetailConfig=JSON.parse(JSON.stringify(libPictSectionFileBrowser.PictViewListDetail.default_configuration));for(let i=0;i<tmpListDetailConfig.Templates.length;i++){if(tmpListDetailConfig.Templates[i].Hash==='FileBrowser-ListDetail-Container-Template'){tmpListDetailConfig.Templates[i].Template=/*html*/`
6034
+ <div class="pict-fb-detail" id="Pict-FileBrowser-DetailList">
6035
+ <div class="pict-fb-breadcrumb-bar">
6036
+ <div class="pict-fb-breadcrumb" id="Pict-FileBrowser-Breadcrumb"></div>
6037
+ <button class="pict-fb-breadcrumb-addfolder" onclick="pict.PictApplication.promptNewFolder()" title="New folder">+</button>
6038
+ </div>
6039
+ <div class="pict-fb-detail-header">
6040
+ <div class="pict-fb-detail-header-cell pict-fb-detail-col-name" onclick="pict.views['{~D:Record.ViewHash~}'].sortBy('Name')">Name</div>
6041
+ <div class="pict-fb-detail-header-cell pict-fb-detail-col-size" onclick="pict.views['{~D:Record.ViewHash~}'].sortBy('Size')">Size</div>
6042
+ <div class="pict-fb-detail-header-cell pict-fb-detail-col-modified" onclick="pict.views['{~D:Record.ViewHash~}'].sortBy('Modified')">Modified</div>
6043
+ </div>
6044
+ <div id="Pict-FileBrowser-DetailRows"></div>
6045
+ </div>
6046
+ `;}if(tmpListDetailConfig.Templates[i].Hash==='FileBrowser-ListDetail-Row-Template'){tmpListDetailConfig.Templates[i].Template=/*html*/`
6047
+ <div class="pict-fb-detail-row{~D:Record.SelectedClass~}" data-index="{~D:Record.Index~}" data-name="{~D:Record.Name~}" onclick="{~D:Record.ClickHandler~}" ondblclick="{~D:Record.DblClickHandler~}">
6048
+ <span class="pict-fb-detail-icon">{~D:Record.Icon~}</span>
6049
+ <span class="pict-fb-detail-name">{~D:Record.Name~}</span>
6050
+ <span class="pict-fb-detail-size">{~D:Record.SizeFormatted~}</span>
6051
+ <span class="pict-fb-detail-modified">{~D:Record.ModifiedFormatted~}</span>
6052
+ <button class="pict-fb-insert-btn" onclick="event.stopPropagation(); pict.PictApplication.insertFileReference(this.parentElement.getAttribute('data-name'))" title="Insert into editor">+</button>
6053
+ </div>
6054
+ `;}}this.pict.addView('Pict-FileBrowser-ListDetail',tmpListDetailConfig,libPictSectionFileBrowser.PictViewListDetail);}onAfterInitializeAsync(fCallback){// Expose the pict instance as window.pict for inline onclick handlers
5898
6055
  // (pict-section-filebrowser templates reference pict.views[...])
5899
6056
  if(typeof window!=='undefined'){window.pict=this.pict;}// Initialize application state
5900
6057
  this.pict.AppData.ContentEditor={CurrentFile:'',ActiveEditor:'markdown',// 'markdown' or 'code'
5901
6058
  IsDirty:false,IsSaving:false,IsLoading:false,Files:[],Document:{Segments:[{Content:''}]},CodeContent:'',SaveStatus:'',SaveStatusClass:'',// Settings
5902
- AutoSegmentMarkdown:false,AutoSegmentDepth:1,AutoContentPreview:false,MarkdownEditingControls:true,MarkdownWordWrap:true,CodeWordWrap:false,SidebarCollapsed:false,SidebarWidth:250,AutoPreviewImages:true,AutoPreviewVideo:false,AutoPreviewAudio:false,ShowHiddenFiles:false,TopicsFilePath:'.pict_documentation_topics.json'};// Restore persisted settings from localStorage
6059
+ AutoSegmentMarkdown:false,AutoSegmentDepth:1,AutoContentPreview:true,MarkdownEditingControls:true,MarkdownWordWrap:true,CodeWordWrap:false,SidebarCollapsed:false,SidebarWidth:250,AutoPreviewImages:true,AutoPreviewVideo:false,AutoPreviewAudio:false,ShowHiddenFiles:false,TopicsFilePath:'.pict_documentation_topics.json'};// Restore persisted settings from localStorage
5903
6060
  this._loadSettings();// Render the layout shell
5904
6061
  this.pict.views['ContentEditor-Layout'].render();// Wire up file selection from the file browser
5905
6062
  let tmpSelf=this;let tmpListProvider=this.pict.providers['Pict-FileBrowser-List'];if(tmpListProvider){let tmpOriginalSelectFile=tmpListProvider.selectFile.bind(tmpListProvider);tmpListProvider.selectFile=function(pFileEntry){tmpOriginalSelectFile(pFileEntry);if(pFileEntry&&pFileEntry.Type==='file'){tmpSelf.navigateToFile(pFileEntry.Path);}};}// Wire up folder navigation to fetch subfolder contents from the server
@@ -6020,10 +6177,15 @@ this._cleanupEditors();// Re-render top bar
6020
6177
  this.pict.views['ContentEditor-TopBar'].render();// Binary files: show preview card without loading content
6021
6178
  if(tmpEditorType==='binary'){this._showBinaryPreview(pFilePath);this.updateStats();return;}// Text files: load content from the server
6022
6179
  let tmpProvider=this.pict.providers['ContentEditor-Provider'];this.pict.AppData.ContentEditor.IsLoading=true;tmpProvider.loadFile(pFilePath,(pError,pContent)=>{tmpSelf.pict.AppData.ContentEditor.IsLoading=false;if(pError){tmpSelf.pict.AppData.ContentEditor.SaveStatus='Error loading file: '+pError;tmpSelf.pict.AppData.ContentEditor.SaveStatusClass='content-editor-status-error';tmpSelf.pict.views['ContentEditor-TopBar'].render();return;}if(tmpEditorType==='markdown'){// Markdown editor: uses segments (auto-segment if enabled)
6023
- tmpSelf.pict.AppData.ContentEditor.Document.Segments=tmpSelf.segmentMarkdownContent(pContent);let tmpEditorView=tmpSelf.pict.views['ContentEditor-MarkdownEditor'];if(tmpEditorView){tmpEditorView.render();tmpEditorView.marshalToView();// Apply the Auto Content Preview setting via the
6024
- // library's own toggle so the per-segment button
6025
- // continues to work.
6026
- tmpEditorView.togglePreview(tmpSelf.pict.AppData.ContentEditor.AutoContentPreview);// Apply the Editing Controls setting (line numbers
6180
+ tmpSelf.pict.AppData.ContentEditor.Document.Segments=tmpSelf.segmentMarkdownContent(pContent);let tmpEditorView=tmpSelf.pict.views['ContentEditor-MarkdownEditor'];if(tmpEditorView){// Set the image base URL so relative image references
6181
+ // resolve through the /content/ static route.
6182
+ let tmpImageBase='/content/';let tmpLastSlash=pFilePath.lastIndexOf('/');if(tmpLastSlash>0){tmpImageBase='/content/'+pFilePath.substring(0,tmpLastSlash)+'/';}tmpEditorView.options.ImageBaseURL=tmpImageBase;tmpEditorView.render();tmpEditorView.marshalToView();// Always ensure the global preview class is clear so
6183
+ // per-segment toggles work.
6184
+ tmpEditorView.togglePreview(true);// Set per-segment preview visibility based on the
6185
+ // Auto Content Preview setting. We must always loop
6186
+ // to clear any stale _hiddenPreviewSegments state
6187
+ // from previous file loads.
6188
+ let tmpShowPreviews=!!tmpSelf.pict.AppData.ContentEditor.AutoContentPreview;for(let tmpIdx in tmpEditorView._segmentEditors){tmpEditorView.toggleSegmentPreview(parseInt(tmpIdx,10),tmpShowPreviews);}// Apply the Editing Controls setting (line numbers
6027
6189
  // and right sidebar) via the library's toggleControls.
6028
6190
  tmpEditorView.toggleControls(tmpSelf.pict.AppData.ContentEditor.MarkdownEditingControls);}tmpSelf.updateStats();}else{// Code editor: uses CodeContent
6029
6191
  tmpSelf.pict.AppData.ContentEditor.CodeContent=pContent;// Detect language from file extension
@@ -6065,6 +6227,24 @@ let tmpDefaultContent='';if(pFilePath.endsWith('.md')){tmpDefaultContent='# '+pF
6065
6227
  tmpSelf.loadFileList(null,()=>{tmpSelf.navigateToFile(pFilePath);});}});}/**
6066
6228
  * Prompt the user for a new file name and create it.
6067
6229
  */promptNewFile(){let tmpFileName=prompt('Enter a name for the new file (e.g. my-page.md, script.js, style.css):');if(tmpFileName&&tmpFileName.trim()){this.createNewFile(tmpFileName.trim());}}/**
6230
+ * Prompt the user for a folder name and create it in the current
6231
+ * browse location via the server API.
6232
+ */promptNewFolder(){let tmpFolderName=prompt('Enter a name for the new folder:');if(!tmpFolderName||!tmpFolderName.trim()){return;}tmpFolderName=tmpFolderName.trim();// Build the full relative path inside the current browse location
6233
+ let tmpCurrentLocation='';if(this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation){tmpCurrentLocation=this.pict.AppData.PictFileBrowser.CurrentLocation;}let tmpPath=tmpCurrentLocation?tmpCurrentLocation+'/'+tmpFolderName:tmpFolderName;let tmpSelf=this;fetch('/api/content/mkdir',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({Path:tmpPath})}).then(pResponse=>pResponse.json()).then(pData=>{if(pData&&pData.Success){tmpSelf.log.info(`Folder created: ${tmpPath}`);// Refresh the file list to show the new folder
6234
+ tmpSelf.loadFileList();}else{alert('Could not create folder: '+(pData?pData.Error:'Unknown error'));}}).catch(pError=>{alert('Error creating folder: '+pError.message);});}/**
6235
+ * Insert a file reference into the active markdown editor segment.
6236
+ *
6237
+ * Called from the "+" button on file browser rows. For image files
6238
+ * this inserts markdown image syntax; for other files a markdown link.
6239
+ *
6240
+ * @param {string} pFilename - The filename to insert
6241
+ */insertFileReference(pFilename){if(!pFilename){return;}let tmpEditorView=this.pict.views['ContentEditor-MarkdownEditor'];if(!tmpEditorView||this.pict.AppData.ContentEditor.ActiveEditor!=='markdown'){return;}// Determine the active segment (the one with focus, or the last one)
6242
+ let tmpSegmentIndex=tmpEditorView._activeSegmentIndex;if(tmpSegmentIndex<0){// Fall back to the first segment
6243
+ let tmpIndices=tmpEditorView._getOrderedSegmentIndices();if(tmpIndices.length>0){tmpSegmentIndex=tmpIndices[0];}}if(tmpSegmentIndex<0){return;}// Build alt text from the filename (strip extension and timestamp prefix)
6244
+ let tmpAltText=pFilename.replace(/\.[^.]+$/,'');tmpAltText=tmpAltText.replace(/^\d{10,}-/,'');tmpAltText=tmpAltText.replace(/[-_]+/g,' ').trim()||'image';// Check if this is an image file
6245
+ let tmpExt=pFilename.substring(pFilename.lastIndexOf('.')).toLowerCase();let tmpImageExts=['.png','.jpg','.jpeg','.gif','.webp','.svg','.bmp','.avif','.apng','.ico','.tiff','.tif','.jfif'];if(tmpImageExts.indexOf(tmpExt)>=0){// Insert markdown image syntax — relative filename resolved by ImageBaseURL
6246
+ tmpEditorView._insertImageMarkdown(tmpSegmentIndex,pFilename,tmpAltText);}else{// Insert a markdown link for non-image files
6247
+ let tmpEditor=tmpEditorView._segmentEditors[tmpSegmentIndex];if(tmpEditor){let tmpInsert='['+tmpAltText+']('+pFilename+')';let tmpCursorPos=tmpEditor.state.selection.main.head;tmpEditor.dispatch({changes:{from:tmpCursorPos,insert:tmpInsert},selection:{anchor:tmpCursorPos+tmpInsert.length}});tmpEditor.focus();}}}/**
6068
6248
  * Handle F4 / Cmd+Shift+T: context-aware topic creation.
6069
6249
  *
6070
6250
  * If the markdown editor is active and has focus, creates a new
@@ -6106,7 +6286,7 @@ tmpStatsEl.textContent='';return;}tmpStatsEl.textContent=tmpLines+' lines \u00B7
6106
6286
  */saveSettings(){if(typeof window==='undefined'||!window.localStorage){return;}let tmpSettings=this.pict.AppData.ContentEditor;let tmpData={AutoSegmentMarkdown:tmpSettings.AutoSegmentMarkdown,AutoSegmentDepth:tmpSettings.AutoSegmentDepth,AutoContentPreview:tmpSettings.AutoContentPreview,MarkdownEditingControls:tmpSettings.MarkdownEditingControls,MarkdownWordWrap:tmpSettings.MarkdownWordWrap,CodeWordWrap:tmpSettings.CodeWordWrap,SidebarCollapsed:tmpSettings.SidebarCollapsed,SidebarWidth:tmpSettings.SidebarWidth,AutoPreviewImages:tmpSettings.AutoPreviewImages,AutoPreviewVideo:tmpSettings.AutoPreviewVideo,AutoPreviewAudio:tmpSettings.AutoPreviewAudio,ShowHiddenFiles:tmpSettings.ShowHiddenFiles,TopicsFilePath:tmpSettings.TopicsFilePath};try{window.localStorage.setItem(this._settingsKey,JSON.stringify(tmpData));}catch(pError){this.log.warn('Failed to save settings: '+pError.message);}}/**
6107
6287
  * Load editor settings from localStorage, overwriting the
6108
6288
  * current defaults for any keys that are present.
6109
- */_loadSettings(){if(typeof window==='undefined'||!window.localStorage){return;}try{let tmpRaw=window.localStorage.getItem(this._settingsKey);if(!tmpRaw){return;}let tmpStored=JSON.parse(tmpRaw);let tmpSettings=this.pict.AppData.ContentEditor;if(typeof tmpStored.AutoSegmentMarkdown==='boolean'){tmpSettings.AutoSegmentMarkdown=tmpStored.AutoSegmentMarkdown;}if(typeof tmpStored.AutoSegmentDepth==='number'){tmpSettings.AutoSegmentDepth=tmpStored.AutoSegmentDepth;}if(typeof tmpStored.AutoContentPreview==='boolean'){tmpSettings.AutoContentPreview=tmpStored.AutoContentPreview;}if(typeof tmpStored.MarkdownEditingControls==='boolean'){tmpSettings.MarkdownEditingControls=tmpStored.MarkdownEditingControls;}if(typeof tmpStored.MarkdownWordWrap==='boolean'){tmpSettings.MarkdownWordWrap=tmpStored.MarkdownWordWrap;}if(typeof tmpStored.CodeWordWrap==='boolean'){tmpSettings.CodeWordWrap=tmpStored.CodeWordWrap;}if(typeof tmpStored.SidebarCollapsed==='boolean'){tmpSettings.SidebarCollapsed=tmpStored.SidebarCollapsed;}if(typeof tmpStored.SidebarWidth==='number'){tmpSettings.SidebarWidth=tmpStored.SidebarWidth;}if(typeof tmpStored.AutoPreviewImages==='boolean'){tmpSettings.AutoPreviewImages=tmpStored.AutoPreviewImages;}if(typeof tmpStored.AutoPreviewVideo==='boolean'){tmpSettings.AutoPreviewVideo=tmpStored.AutoPreviewVideo;}if(typeof tmpStored.AutoPreviewAudio==='boolean'){tmpSettings.AutoPreviewAudio=tmpStored.AutoPreviewAudio;}if(typeof tmpStored.ShowHiddenFiles==='boolean'){tmpSettings.ShowHiddenFiles=tmpStored.ShowHiddenFiles;}if(typeof tmpStored.TopicsFilePath==='string'){tmpSettings.TopicsFilePath=tmpStored.TopicsFilePath;}}catch(pError){this.log.warn('Failed to load settings: '+pError.message);}}}module.exports=ContentEditorApplication;module.exports.default_configuration=require('./Pict-Application-ContentEditor-Configuration.json');},{"./Pict-Application-ContentEditor-Configuration.json":96,"./providers/Pict-Provider-ContentEditor.js":101,"./views/PictView-Editor-CodeEditor.js":102,"./views/PictView-Editor-Layout.js":103,"./views/PictView-Editor-MarkdownEditor.js":104,"./views/PictView-Editor-MarkdownReference.js":105,"./views/PictView-Editor-SettingsPanel.js":106,"./views/PictView-Editor-TopBar.js":107,"./views/PictView-Editor-Topics.js":108,"pict-application":44,"pict-section-filebrowser":54}],98:[function(require,module,exports){module.exports={"Name":"Retold Content Reader","Hash":"ContentReader","MainViewportViewIdentifier":"Docuserve-Layout","AutoSolveAfterInitialize":true,"AutoRenderMainViewportViewAfterInitialize":false,"AutoRenderViewsAfterInitialize":false,"pict_configuration":{"Product":"ContentReader-Pict-Application"}};},{}],99:[function(require,module,exports){const libDocuserveApplication=require('pict-docuserve');/**
6289
+ */_loadSettings(){if(typeof window==='undefined'||!window.localStorage){return;}try{let tmpRaw=window.localStorage.getItem(this._settingsKey);if(!tmpRaw){return;}let tmpStored=JSON.parse(tmpRaw);let tmpSettings=this.pict.AppData.ContentEditor;if(typeof tmpStored.AutoSegmentMarkdown==='boolean'){tmpSettings.AutoSegmentMarkdown=tmpStored.AutoSegmentMarkdown;}if(typeof tmpStored.AutoSegmentDepth==='number'){tmpSettings.AutoSegmentDepth=tmpStored.AutoSegmentDepth;}if(typeof tmpStored.AutoContentPreview==='boolean'){tmpSettings.AutoContentPreview=tmpStored.AutoContentPreview;}if(typeof tmpStored.MarkdownEditingControls==='boolean'){tmpSettings.MarkdownEditingControls=tmpStored.MarkdownEditingControls;}if(typeof tmpStored.MarkdownWordWrap==='boolean'){tmpSettings.MarkdownWordWrap=tmpStored.MarkdownWordWrap;}if(typeof tmpStored.CodeWordWrap==='boolean'){tmpSettings.CodeWordWrap=tmpStored.CodeWordWrap;}if(typeof tmpStored.SidebarCollapsed==='boolean'){tmpSettings.SidebarCollapsed=tmpStored.SidebarCollapsed;}if(typeof tmpStored.SidebarWidth==='number'){tmpSettings.SidebarWidth=tmpStored.SidebarWidth;}if(typeof tmpStored.AutoPreviewImages==='boolean'){tmpSettings.AutoPreviewImages=tmpStored.AutoPreviewImages;}if(typeof tmpStored.AutoPreviewVideo==='boolean'){tmpSettings.AutoPreviewVideo=tmpStored.AutoPreviewVideo;}if(typeof tmpStored.AutoPreviewAudio==='boolean'){tmpSettings.AutoPreviewAudio=tmpStored.AutoPreviewAudio;}if(typeof tmpStored.ShowHiddenFiles==='boolean'){tmpSettings.ShowHiddenFiles=tmpStored.ShowHiddenFiles;}if(typeof tmpStored.TopicsFilePath==='string'){tmpSettings.TopicsFilePath=tmpStored.TopicsFilePath;}}catch(pError){this.log.warn('Failed to load settings: '+pError.message);}}}module.exports=ContentEditorApplication;module.exports.default_configuration=require('./Pict-Application-ContentEditor-Configuration.json');},{"./Pict-Application-ContentEditor-Configuration.json":96,"./providers/Pict-Provider-ContentEditor.js":101,"./views/PictView-Editor-CodeEditor.js":102,"./views/PictView-Editor-Layout.js":103,"./views/PictView-Editor-MarkdownEditor.js":104,"./views/PictView-Editor-MarkdownReference.js":105,"./views/PictView-Editor-SettingsPanel.js":106,"./views/PictView-Editor-TopBar.js":107,"./views/PictView-Editor-Topics.js":108,"pict-application":44,"pict-section-filebrowser":51}],98:[function(require,module,exports){module.exports={"Name":"Retold Content Reader","Hash":"ContentReader","MainViewportViewIdentifier":"Docuserve-Layout","AutoSolveAfterInitialize":true,"AutoRenderMainViewportViewAfterInitialize":false,"AutoRenderViewsAfterInitialize":false,"pict_configuration":{"Product":"ContentReader-Pict-Application"}};},{}],99:[function(require,module,exports){const libDocuserveApplication=require('pict-docuserve');/**
6110
6290
  * Content Reader Application
6111
6291
  *
6112
6292
  * Extends pict-docuserve to serve standalone markdown content.
@@ -6156,11 +6336,7 @@ if(typeof window!=='undefined'){window.PictContentReader=module.exports.PictCont
6156
6336
  * @param {File} pFile - The image file to upload
6157
6337
  * @param {Function} fCallback - Callback receiving (error, url)
6158
6338
  */uploadImage(pFile,fCallback){let tmpCallback=typeof fCallback==='function'?fCallback:()=>{};// Determine the target folder from the currently open file
6159
- let tmpUploadPath='';let tmpCurrentFile=this.pict.AppData.ContentEditor.CurrentFile;if(tmpCurrentFile){let tmpLastSlash=tmpCurrentFile.lastIndexOf('/');if(tmpLastSlash>0){tmpUploadPath=tmpCurrentFile.substring(0,tmpLastSlash);}}else if(this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation){tmpUploadPath=this.pict.AppData.PictFileBrowser.CurrentLocation;}let tmpHeaders={'Content-Type':pFile.type,'x-filename':pFile.name};if(tmpUploadPath){tmpHeaders['x-upload-path']=tmpUploadPath;}fetch('/api/content/upload-image',{method:'POST',body:pFile,headers:tmpHeaders}).then(pResponse=>pResponse.json()).then(pData=>{if(pData&&pData.Success&&pData.URL){return tmpCallback(null,pData.URL);}return tmpCallback(pData?pData.Error:'Upload failed');}).catch(pError=>{this.log.warn(`ContentEditor: Image upload failed: ${pError}`);return tmpCallback(pError.message);});}/**
6160
- * List uploaded images.
6161
- *
6162
- * @param {Function} fCallback - Callback receiving (error, filesArray)
6163
- */listUploads(fCallback){let tmpCallback=typeof fCallback==='function'?fCallback:()=>{};fetch('/api/content/uploads').then(pResponse=>pResponse.json()).then(pData=>{if(pData&&pData.Success){return tmpCallback(null,pData.Files||[]);}return tmpCallback(pData?pData.Error:'Unknown error',[]);}).catch(pError=>{this.log.warn(`ContentEditor: Error listing uploads: ${pError}`);return tmpCallback(pError.message,[]);});}}module.exports=ContentEditorProvider;module.exports.default_configuration={ProviderIdentifier:"ContentEditor-Provider",AutoInitialize:true,AutoInitializeOrdinal:0};},{"pict-provider":46}],102:[function(require,module,exports){const libPictSectionCode=require('pict-section-code');/**
6339
+ let tmpUploadPath='';let tmpCurrentFile=this.pict.AppData.ContentEditor.CurrentFile;if(tmpCurrentFile){let tmpLastSlash=tmpCurrentFile.lastIndexOf('/');if(tmpLastSlash>0){tmpUploadPath=tmpCurrentFile.substring(0,tmpLastSlash);}}else if(this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation){tmpUploadPath=this.pict.AppData.PictFileBrowser.CurrentLocation;}let tmpHeaders={'Content-Type':pFile.type,'x-filename':pFile.name};if(tmpUploadPath){tmpHeaders['x-upload-path']=tmpUploadPath;}fetch('/api/content/upload-image',{method:'POST',body:pFile,headers:tmpHeaders}).then(pResponse=>pResponse.json()).then(pData=>{if(pData&&pData.Success&&pData.URL){return tmpCallback(null,pData.URL);}return tmpCallback(pData?pData.Error:'Upload failed');}).catch(pError=>{this.log.warn(`ContentEditor: Image upload failed: ${pError}`);return tmpCallback(pError.message);});}}module.exports=ContentEditorProvider;module.exports.default_configuration={ProviderIdentifier:"ContentEditor-Provider",AutoInitialize:true,AutoInitializeOrdinal:0};},{"pict-provider":46}],102:[function(require,module,exports){const libPictSectionCode=require('pict-section-code');/**
6164
6340
  * Map of file extensions to highlight.js language identifiers.
6165
6341
  *
6166
6342
  * highlight.js supports 190+ languages. This map covers the most common
@@ -6388,6 +6564,38 @@ if(this.pict.PictApplication){this.pict.PictApplication.markDirty();this.pict.Pi
6388
6564
  {
6389
6565
  display: none;
6390
6566
  }
6567
+ /* Breadcrumb bar: flex wrapper with create-folder button */
6568
+ .pict-fb-breadcrumb-bar
6569
+ {
6570
+ display: flex;
6571
+ align-items: center;
6572
+ background: #F5F0E8;
6573
+ border-bottom: 1px solid #DDD6CA;
6574
+ }
6575
+ .pict-fb-breadcrumb-bar .pict-fb-breadcrumb
6576
+ {
6577
+ flex: 1;
6578
+ border-bottom: none;
6579
+ }
6580
+ .pict-fb-breadcrumb-addfolder
6581
+ {
6582
+ flex-shrink: 0;
6583
+ background: transparent;
6584
+ border: none;
6585
+ font-size: 1.1rem;
6586
+ font-weight: 600;
6587
+ color: #8A7F72;
6588
+ cursor: pointer;
6589
+ padding: 4px 10px;
6590
+ line-height: 1;
6591
+ border-radius: 4px;
6592
+ margin-right: 4px;
6593
+ }
6594
+ .pict-fb-breadcrumb-addfolder:hover
6595
+ {
6596
+ color: #2E7D74;
6597
+ background: #EAE3D8;
6598
+ }
6391
6599
  #ContentEditor-Editor-Container
6392
6600
  {
6393
6601
  flex: 1;
@@ -6724,6 +6932,171 @@ if(this.pict.PictApplication){this.pict.PictApplication.markDirty();this.pict.Pi
6724
6932
  color: #8A7F72;
6725
6933
  text-align: center;
6726
6934
  }
6935
+
6936
+ /* File browser row insert button — hidden by default, shown on
6937
+ hover for image file rows via CSS attribute selectors. */
6938
+ .pict-fb-insert-btn
6939
+ {
6940
+ display: none;
6941
+ position: absolute;
6942
+ right: 6px;
6943
+ top: 50%;
6944
+ transform: translateY(-50%);
6945
+ background: #2E7D74;
6946
+ color: #FFF;
6947
+ border: none;
6948
+ border-radius: 4px;
6949
+ font-size: 0.78rem;
6950
+ font-weight: 700;
6951
+ line-height: 1;
6952
+ padding: 2px 7px;
6953
+ cursor: pointer;
6954
+ }
6955
+ .pict-fb-insert-btn:hover
6956
+ {
6957
+ background: #3A9E92;
6958
+ }
6959
+ /* Make the row position:relative so the button can be absolutely placed */
6960
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row
6961
+ {
6962
+ position: relative;
6963
+ }
6964
+ /* Show the insert button on hover for image file extensions.
6965
+ CSS attribute selector [data-name$=".ext" i] matches
6966
+ the end of the data-name attribute, case-insensitive. */
6967
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".png" i]:hover .pict-fb-insert-btn,
6968
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".jpg" i]:hover .pict-fb-insert-btn,
6969
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".jpeg" i]:hover .pict-fb-insert-btn,
6970
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".gif" i]:hover .pict-fb-insert-btn,
6971
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".webp" i]:hover .pict-fb-insert-btn,
6972
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".svg" i]:hover .pict-fb-insert-btn,
6973
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".bmp" i]:hover .pict-fb-insert-btn,
6974
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".avif" i]:hover .pict-fb-insert-btn,
6975
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".apng" i]:hover .pict-fb-insert-btn,
6976
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".ico" i]:hover .pict-fb-insert-btn,
6977
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".tiff" i]:hover .pict-fb-insert-btn,
6978
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".tif" i]:hover .pict-fb-insert-btn,
6979
+ #ContentEditor-Sidebar-Container .pict-fb-detail-row[data-name$=".jfif" i]:hover .pict-fb-insert-btn
6980
+ {
6981
+ display: inline-block;
6982
+ }
6983
+
6984
+ /* ============================================
6985
+ RESPONSIVE: Tablet / Phone (max-width: 768px)
6986
+ ============================================ */
6987
+ @media (max-width: 768px)
6988
+ {
6989
+ /* Prevent horizontal scroll on the whole app */
6990
+ #ContentEditor-Application-Container
6991
+ {
6992
+ overflow-x: hidden;
6993
+ width: 100%;
6994
+ max-width: 100vw;
6995
+ }
6996
+
6997
+ /* Stack sidebar ABOVE editor instead of side-by-side */
6998
+ .content-editor-body
6999
+ {
7000
+ flex-direction: column;
7001
+ }
7002
+
7003
+ /* Sidebar becomes a horizontal strip at the top */
7004
+ .content-editor-sidebar-wrap
7005
+ {
7006
+ width: 100% !important;
7007
+ max-height: 40vh;
7008
+ flex-shrink: 0;
7009
+ border-bottom: 1px solid #DDD6CA;
7010
+ border-right: none;
7011
+ }
7012
+
7013
+ /* When collapsed on mobile, hide the inner content but keep the
7014
+ toggle button visible (it's positioned below the sidebar strip) */
7015
+ .content-editor-sidebar-wrap.collapsed
7016
+ {
7017
+ width: 100% !important;
7018
+ max-height: 0;
7019
+ overflow: visible;
7020
+ }
7021
+ .content-editor-sidebar-wrap.collapsed .content-editor-sidebar-inner
7022
+ {
7023
+ display: none;
7024
+ }
7025
+
7026
+ /* Hide the resize handle (desktop-only interaction) */
7027
+ .content-editor-resize-handle
7028
+ {
7029
+ display: none;
7030
+ }
7031
+
7032
+ /* Reposition the sidebar toggle for horizontal layout —
7033
+ place it at the bottom-center of the sidebar strip */
7034
+ .content-editor-sidebar-toggle
7035
+ {
7036
+ position: absolute;
7037
+ top: auto;
7038
+ bottom: -20px;
7039
+ right: auto;
7040
+ left: 50%;
7041
+ transform: translateX(-50%);
7042
+ width: 28px;
7043
+ height: 20px;
7044
+ border-radius: 0 0 4px 4px;
7045
+ border: 1px solid #DDD6CA;
7046
+ border-top: none;
7047
+ z-index: 10;
7048
+ }
7049
+ .content-editor-sidebar-wrap.collapsed .content-editor-sidebar-toggle
7050
+ {
7051
+ bottom: -20px;
7052
+ right: auto;
7053
+ left: 50%;
7054
+ transform: translateX(-50%);
7055
+ }
7056
+
7057
+ /* Reduce editor container padding (less gutters) */
7058
+ #ContentEditor-Editor-Container
7059
+ {
7060
+ padding: 24px 8px 8px 8px;
7061
+ }
7062
+
7063
+ /* Reduce binary preview padding */
7064
+ .binary-preview-image
7065
+ {
7066
+ padding: 12px;
7067
+ }
7068
+ .binary-preview-card
7069
+ {
7070
+ padding: 12px;
7071
+ gap: 12px;
7072
+ }
7073
+
7074
+ /* Upload panel: fill more of the screen */
7075
+ .content-editor-upload-panel
7076
+ {
7077
+ width: 95vw;
7078
+ max-width: 95vw;
7079
+ }
7080
+
7081
+ }
7082
+
7083
+ /* ============================================
7084
+ RESPONSIVE: Small phone (max-width: 480px)
7085
+ ============================================ */
7086
+ @media (max-width: 480px)
7087
+ {
7088
+ /* Even tighter editor padding */
7089
+ #ContentEditor-Editor-Container
7090
+ {
7091
+ padding: 20px 4px 4px 4px;
7092
+ }
7093
+
7094
+ /* Sidebar gets a smaller max height */
7095
+ .content-editor-sidebar-wrap
7096
+ {
7097
+ max-height: 35vh;
7098
+ }
7099
+ }
6727
7100
  `,Templates:[{Hash:"ContentEditor-Layout-Shell-Template",Template:/*html*/`
6728
7101
  <div id="ContentEditor-TopBar-Container"></div>
6729
7102
  <div class="content-editor-body">
@@ -6779,7 +7152,10 @@ if(this.pict.PictApplication){this.pict.PictApplication.markDirty();this.pict.Pi
6779
7152
  this.pict.views['ContentEditor-TopBar'].render();// Show welcome message in editor area if no file loaded
6780
7153
  let tmpEditorContainer=this.pict.ContentAssignment.getElement('#ContentEditor-Editor-Container');if(tmpEditorContainer&&tmpEditorContainer[0]&&!this.pict.AppData.ContentEditor.CurrentFile){tmpEditorContainer[0].innerHTML='<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#8A7F72;font-size:1.1em;">Select a file from the sidebar to begin editing</div>';}// Inject CSS
6781
7154
  this.pict.CSSMap.injectCSS();// Apply persisted sidebar state
6782
- let tmpSettings=this.pict.AppData.ContentEditor;let tmpWrap=document.getElementById('ContentEditor-SidebarWrap');let tmpToggle=document.getElementById('ContentEditor-SidebarToggle');if(tmpWrap){tmpWrap.style.width=tmpSettings.SidebarWidth+'px';if(tmpSettings.SidebarCollapsed){tmpWrap.classList.add('collapsed');if(tmpToggle)tmpToggle.innerHTML='&#x25B6;';}}// Wire up sidebar toggle
7155
+ let tmpSettings=this.pict.AppData.ContentEditor;let tmpWrap=document.getElementById('ContentEditor-SidebarWrap');let tmpToggle=document.getElementById('ContentEditor-SidebarToggle');if(tmpWrap){let tmpIsMobile=window.innerWidth<=768;tmpWrap.style.width=tmpIsMobile?'100%':tmpSettings.SidebarWidth+'px';if(tmpSettings.SidebarCollapsed){tmpWrap.classList.add('collapsed');if(tmpToggle)tmpToggle.innerHTML=tmpIsMobile?'&#x25BC;':'&#x25B6;';}else if(tmpIsMobile){// Auto-collapse sidebar on narrow viewports.
7156
+ // Sync the setting so toggleSidebar() works correctly,
7157
+ // but don't persist — the desktop preference stays in localStorage.
7158
+ tmpSettings.SidebarCollapsed=true;tmpWrap.classList.add('collapsed');if(tmpToggle)tmpToggle.innerHTML='&#x25BC;';}}// Wire up sidebar toggle
6783
7159
  let tmpSelf=this;if(tmpToggle){tmpToggle.addEventListener('click',()=>{tmpSelf.toggleSidebar();});}// Wire up resize handle
6784
7160
  this._wireResizeHandle();// Listen for hash changes
6785
7161
  window.addEventListener('hashchange',()=>{tmpSelf.pict.PictApplication.resolveHash();});// Keyboard shortcuts
@@ -6796,7 +7172,8 @@ let tmpUploadOverlay=document.getElementById('ContentEditor-UploadOverlay');if(t
6796
7172
  let tmpConfirmOverlay=document.getElementById('ContentEditor-ConfirmOverlay');if(tmpConfirmOverlay&&tmpConfirmOverlay.classList.contains('open')){return;}// Close the current file
6797
7173
  if(tmpSelf.pict.AppData.ContentEditor.CurrentFile){pEvent.preventDefault();tmpSelf.pict.PictApplication.closeCurrentFile();return;}}});return super.onAfterRender(pRenderable,pRenderDestinationAddress,pRecord,pContent);}/**
6798
7174
  * Toggle the sidebar collapsed/expanded state.
6799
- */toggleSidebar(){let tmpWrap=document.getElementById('ContentEditor-SidebarWrap');let tmpToggle=document.getElementById('ContentEditor-SidebarToggle');if(!tmpWrap){return;}let tmpSettings=this.pict.AppData.ContentEditor;tmpSettings.SidebarCollapsed=!tmpSettings.SidebarCollapsed;if(tmpSettings.SidebarCollapsed){tmpWrap.classList.add('collapsed');if(tmpToggle)tmpToggle.innerHTML='&#x25B6;';}else{tmpWrap.classList.remove('collapsed');tmpWrap.style.width=tmpSettings.SidebarWidth+'px';if(tmpToggle)tmpToggle.innerHTML='&#x25C0;';}this.pict.PictApplication.saveSettings();}/**
7175
+ */toggleSidebar(){let tmpWrap=document.getElementById('ContentEditor-SidebarWrap');let tmpToggle=document.getElementById('ContentEditor-SidebarToggle');if(!tmpWrap){return;}let tmpSettings=this.pict.AppData.ContentEditor;tmpSettings.SidebarCollapsed=!tmpSettings.SidebarCollapsed;// Use vertical arrows on narrow viewports, horizontal on wide
7176
+ let tmpIsMobile=window.innerWidth<=768;if(tmpSettings.SidebarCollapsed){tmpWrap.classList.add('collapsed');if(tmpToggle)tmpToggle.innerHTML=tmpIsMobile?'&#x25BC;':'&#x25B6;';}else{tmpWrap.classList.remove('collapsed');tmpWrap.style.width=tmpIsMobile?'100%':tmpSettings.SidebarWidth+'px';if(tmpToggle)tmpToggle.innerHTML=tmpIsMobile?'&#x25B2;':'&#x25C0;';}this.pict.PictApplication.saveSettings();}/**
6800
7177
  * Switch the active sidebar tab.
6801
7178
  *
6802
7179
  * @param {string} pTab - 'files', 'reference', or 'topics'
@@ -7550,6 +7927,40 @@ if(tmpLastIndex<tmpText.length){tmpFragment.appendChild(document.createTextNode(
7550
7927
  color: #5E5549;
7551
7928
  white-space: nowrap;
7552
7929
  }
7930
+
7931
+ /* ============================================
7932
+ RESPONSIVE: Tablet / Phone (max-width: 768px)
7933
+ ============================================ */
7934
+ @media (max-width: 768px)
7935
+ {
7936
+ /* Settings flyout: position from left edge for more room */
7937
+ .content-editor-settings-flyout
7938
+ {
7939
+ right: -8px;
7940
+ width: 260px;
7941
+ }
7942
+ }
7943
+
7944
+ /* ============================================
7945
+ RESPONSIVE: Small phone (max-width: 480px)
7946
+ ============================================ */
7947
+ @media (max-width: 480px)
7948
+ {
7949
+ /* Full-width settings flyout on small phones */
7950
+ .content-editor-settings-flyout
7951
+ {
7952
+ position: fixed;
7953
+ top: 48px;
7954
+ right: 0;
7955
+ left: 0;
7956
+ width: 100%;
7957
+ border-radius: 0 0 8px 8px;
7958
+ }
7959
+ .content-editor-settings-flyout::before
7960
+ {
7961
+ display: none;
7962
+ }
7963
+ }
7553
7964
  `,Templates:[{Hash:"ContentEditor-SettingsPanel-Template",Template:/*html*/`
7554
7965
  <div class="content-editor-settings-wrap">
7555
7966
  <button class="content-editor-settings-gear" id="ContentEditor-SettingsGear"
@@ -7665,7 +8076,10 @@ if(tmpLastIndex<tmpText.length){tmpFragment.appendChild(document.createTextNode(
7665
8076
  let tmpSettings=this.pict.AppData.ContentEditor;let tmpMdWrapCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-MarkdownWordWrap');if(tmpMdWrapCheckbox&&tmpMdWrapCheckbox[0]){tmpMdWrapCheckbox[0].checked=tmpSettings.MarkdownWordWrap;}let tmpCodeWrapCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-CodeWordWrap');if(tmpCodeWrapCheckbox&&tmpCodeWrapCheckbox[0]){tmpCodeWrapCheckbox[0].checked=tmpSettings.CodeWordWrap;}let tmpControlsCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-EditingControls');if(tmpControlsCheckbox&&tmpControlsCheckbox[0]){tmpControlsCheckbox[0].checked=tmpSettings.MarkdownEditingControls;}let tmpPreviewCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-AutoPreview');if(tmpPreviewCheckbox&&tmpPreviewCheckbox[0]){tmpPreviewCheckbox[0].checked=tmpSettings.AutoContentPreview;}let tmpSegmentCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-AutoSegment');if(tmpSegmentCheckbox&&tmpSegmentCheckbox[0]){tmpSegmentCheckbox[0].checked=tmpSettings.AutoSegmentMarkdown;}let tmpSelect=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-SegmentDepth');if(tmpSelect&&tmpSelect[0]){tmpSelect[0].value=String(tmpSettings.AutoSegmentDepth);tmpSelect[0].disabled=!tmpSettings.AutoSegmentMarkdown;}let tmpImgCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-AutoPreviewImages');if(tmpImgCheckbox&&tmpImgCheckbox[0]){tmpImgCheckbox[0].checked=tmpSettings.AutoPreviewImages;}let tmpVideoCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-AutoPreviewVideo');if(tmpVideoCheckbox&&tmpVideoCheckbox[0]){tmpVideoCheckbox[0].checked=tmpSettings.AutoPreviewVideo;}let tmpAudioCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-AutoPreviewAudio');if(tmpAudioCheckbox&&tmpAudioCheckbox[0]){tmpAudioCheckbox[0].checked=tmpSettings.AutoPreviewAudio;}let tmpHiddenCheckbox=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-ShowHiddenFiles');if(tmpHiddenCheckbox&&tmpHiddenCheckbox[0]){tmpHiddenCheckbox[0].checked=tmpSettings.ShowHiddenFiles;}return super.onAfterRender(pRenderable,pRenderDestinationAddress,pRecord,pContent);}togglePanel(){if(this._isOpen){this.closePanel();}else{this.openPanel();}}openPanel(){this._isOpen=true;let tmpFlyout=this.pict.ContentAssignment.getElement('#ContentEditor-SettingsFlyout');let tmpOverlay=this.pict.ContentAssignment.getElement('#ContentEditor-SettingsOverlay');let tmpGear=this.pict.ContentAssignment.getElement('#ContentEditor-SettingsGear');if(tmpFlyout&&tmpFlyout[0])tmpFlyout[0].classList.add('open');if(tmpOverlay&&tmpOverlay[0])tmpOverlay[0].classList.add('open');if(tmpGear&&tmpGear[0])tmpGear[0].classList.add('active');}closePanel(){this._isOpen=false;let tmpFlyout=this.pict.ContentAssignment.getElement('#ContentEditor-SettingsFlyout');let tmpOverlay=this.pict.ContentAssignment.getElement('#ContentEditor-SettingsOverlay');let tmpGear=this.pict.ContentAssignment.getElement('#ContentEditor-SettingsGear');if(tmpFlyout&&tmpFlyout[0])tmpFlyout[0].classList.remove('open');if(tmpOverlay&&tmpOverlay[0])tmpOverlay[0].classList.remove('open');if(tmpGear&&tmpGear[0])tmpGear[0].classList.remove('active');}onMarkdownWordWrapChanged(pChecked){this.pict.AppData.ContentEditor.MarkdownWordWrap=pChecked;this.pict.PictApplication.saveSettings();// Live-apply to all CodeMirror segment editors if markdown is active
7666
8077
  let tmpEditorView=this.pict.views['ContentEditor-MarkdownEditor'];if(tmpEditorView&&this.pict.AppData.ContentEditor.ActiveEditor==='markdown'&&tmpEditorView._segmentEditors){for(let tmpKey in tmpEditorView._segmentEditors){let tmpEditor=tmpEditorView._segmentEditors[tmpKey];if(tmpEditor&&tmpEditor.contentDOM){if(pChecked){tmpEditor.contentDOM.classList.add('cm-lineWrapping');}else{tmpEditor.contentDOM.classList.remove('cm-lineWrapping');}}}}}onCodeWordWrapChanged(pChecked){this.pict.AppData.ContentEditor.CodeWordWrap=pChecked;this.pict.PictApplication.saveSettings();// Live-apply to the code editor if it's currently active
7667
8078
  let tmpCodeEditorView=this.pict.views['ContentEditor-CodeEditor'];if(tmpCodeEditorView&&tmpCodeEditorView._editorElement&&this.pict.AppData.ContentEditor.ActiveEditor==='code'){if(pChecked){tmpCodeEditorView._editorElement.style.whiteSpace='pre-wrap';tmpCodeEditorView._editorElement.style.overflowWrap='break-word';}else{tmpCodeEditorView._editorElement.style.whiteSpace='pre';tmpCodeEditorView._editorElement.style.overflowWrap='normal';}}}onEditingControlsChanged(pChecked){this.pict.AppData.ContentEditor.MarkdownEditingControls=pChecked;this.pict.PictApplication.saveSettings();// Live-apply to the markdown editor if it's currently active
7668
- let tmpEditorView=this.pict.views['ContentEditor-MarkdownEditor'];if(tmpEditorView&&this.pict.AppData.ContentEditor.ActiveEditor==='markdown'){tmpEditorView.toggleControls(pChecked);}}onAutoPreviewChanged(pChecked){this.pict.AppData.ContentEditor.AutoContentPreview=pChecked;this.pict.PictApplication.saveSettings();}onAutoSegmentChanged(pChecked){this.pict.AppData.ContentEditor.AutoSegmentMarkdown=pChecked;this.pict.PictApplication.saveSettings();// Enable/disable the depth dropdown
8079
+ let tmpEditorView=this.pict.views['ContentEditor-MarkdownEditor'];if(tmpEditorView&&this.pict.AppData.ContentEditor.ActiveEditor==='markdown'){tmpEditorView.toggleControls(pChecked);}}onAutoPreviewChanged(pChecked){this.pict.AppData.ContentEditor.AutoContentPreview=pChecked;this.pict.PictApplication.saveSettings();let tmpEditorView=this.pict.views['ContentEditor-MarkdownEditor'];if(tmpEditorView&&this.pict.AppData.ContentEditor.ActiveEditor==='markdown'){if(pChecked){// Turning ON: clear global hidden class and show all previews
8080
+ tmpEditorView.togglePreview(true);for(let tmpIdx in tmpEditorView._segmentEditors){tmpEditorView.toggleSegmentPreview(parseInt(tmpIdx,10),true);}}else{// Turning OFF: hide each segment individually so
8081
+ // per-segment toggle buttons still work
8082
+ for(let tmpIdx in tmpEditorView._segmentEditors){tmpEditorView.toggleSegmentPreview(parseInt(tmpIdx,10),false);}}}}onAutoSegmentChanged(pChecked){this.pict.AppData.ContentEditor.AutoSegmentMarkdown=pChecked;this.pict.PictApplication.saveSettings();// Enable/disable the depth dropdown
7669
8083
  let tmpSelect=this.pict.ContentAssignment.getElement('#ContentEditor-Setting-SegmentDepth');if(tmpSelect&&tmpSelect[0]){tmpSelect[0].disabled=!pChecked;}}onSegmentDepthChanged(pValue){this.pict.AppData.ContentEditor.AutoSegmentDepth=parseInt(pValue,10)||1;this.pict.PictApplication.saveSettings();}onAutoPreviewImagesChanged(pChecked){this.pict.AppData.ContentEditor.AutoPreviewImages=pChecked;this.pict.PictApplication.saveSettings();}onAutoPreviewVideoChanged(pChecked){this.pict.AppData.ContentEditor.AutoPreviewVideo=pChecked;this.pict.PictApplication.saveSettings();}onAutoPreviewAudioChanged(pChecked){this.pict.AppData.ContentEditor.AutoPreviewAudio=pChecked;this.pict.PictApplication.saveSettings();}onShowHiddenFilesChanged(pChecked){this.pict.AppData.ContentEditor.ShowHiddenFiles=pChecked;this.pict.PictApplication.saveSettings();// Tell the server to include/exclude hidden files, then refresh
7670
8084
  let tmpSelf=this;this.pict.PictApplication.syncHiddenFilesSetting(()=>{tmpSelf.pict.PictApplication.loadFileList();});}}module.exports=ContentEditorSettingsPanelView;module.exports.default_configuration=_ViewConfiguration;},{"pict-view":76}],107:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={ViewIdentifier:"ContentEditor-TopBar",DefaultRenderable:"ContentEditor-TopBar-Display",DefaultDestinationAddress:"#ContentEditor-TopBar-Container",AutoRender:false,CSS:/*css*/`
7671
8085
  .content-editor-topbar
@@ -7896,6 +8310,99 @@ let tmpSelf=this;this.pict.PictApplication.syncHiddenFilesSetting(()=>{tmpSelf.p
7896
8310
  border-radius: 3px;
7897
8311
  color: #5E5549;
7898
8312
  }
8313
+
8314
+ /* ============================================
8315
+ RESPONSIVE: Tablet / Phone (max-width: 768px)
8316
+ ============================================ */
8317
+ @media (max-width: 768px)
8318
+ {
8319
+ .content-editor-topbar
8320
+ {
8321
+ height: auto;
8322
+ min-height: 44px;
8323
+ flex-wrap: wrap;
8324
+ padding: 4px 0;
8325
+ }
8326
+
8327
+ /* Brand text: shrink */
8328
+ .content-editor-topbar-brand
8329
+ {
8330
+ padding: 0 8px;
8331
+ font-size: 0.85rem;
8332
+ }
8333
+
8334
+ /* File name: switch from absolute centering to flow layout */
8335
+ .content-editor-topbar-file
8336
+ {
8337
+ position: static;
8338
+ transform: none;
8339
+ max-width: none;
8340
+ flex: 1;
8341
+ min-width: 0;
8342
+ padding: 0 4px;
8343
+ }
8344
+
8345
+ /* Hide the spacer (not needed when file is in-flow) */
8346
+ .content-editor-topbar-spacer
8347
+ {
8348
+ display: none;
8349
+ }
8350
+
8351
+ /* Hide word/line count stats on mobile to save space */
8352
+ .content-editor-topbar-stats
8353
+ {
8354
+ display: none;
8355
+ }
8356
+
8357
+ /* Compact action buttons */
8358
+ .content-editor-topbar-actions
8359
+ {
8360
+ gap: 4px;
8361
+ padding: 0 6px;
8362
+ }
8363
+
8364
+ .content-editor-topbar-btn
8365
+ {
8366
+ padding: 5px 10px;
8367
+ font-size: 0.75rem;
8368
+ }
8369
+
8370
+ /* Status text */
8371
+ .content-editor-topbar-status
8372
+ {
8373
+ padding: 0 6px;
8374
+ font-size: 0.72rem;
8375
+ }
8376
+
8377
+ /* Confirm dialog: tighter for mobile */
8378
+ .content-editor-confirm-panel
8379
+ {
8380
+ width: 95vw;
8381
+ max-width: 95vw;
8382
+ }
8383
+ }
8384
+
8385
+ /* ============================================
8386
+ RESPONSIVE: Small phone (max-width: 480px)
8387
+ ============================================ */
8388
+ @media (max-width: 480px)
8389
+ {
8390
+ /* Hide brand text entirely on very small screens */
8391
+ .content-editor-topbar-brand
8392
+ {
8393
+ display: none;
8394
+ }
8395
+
8396
+ .content-editor-topbar-filename
8397
+ {
8398
+ font-size: 0.8rem;
8399
+ }
8400
+
8401
+ .content-editor-topbar-actions
8402
+ {
8403
+ padding: 0 4px;
8404
+ }
8405
+ }
7899
8406
  `,Templates:[{Hash:"ContentEditor-TopBar-Template",Template:/*html*/`
7900
8407
  <div class="content-editor-topbar">
7901
8408
  <div class="content-editor-topbar-brand">Content Editor</div>
@@ -8541,7 +9048,7 @@ if(psychotic){result.hostname=isAbsolute?'':srcPath.length?srcPath.shift():'';re
8541
9048
  if(result.pathname!==null||result.search!==null){result.path=(result.pathname?result.pathname:'')+(result.search?result.search:'');}result.auth=relative.auth||result.auth;result.slashes=result.slashes||relative.slashes;result.href=result.format();return result;};Url.prototype.parseHost=function(){var host=this.host;var port=portPattern.exec(host);if(port){port=port[0];if(port!==':'){this.port=port.substr(1);}host=host.substr(0,host.length-port.length);}if(host){this.hostname=host;}};exports.parse=urlParse;exports.resolve=urlResolve;exports.resolveObject=urlResolveObject;exports.format=urlFormat;exports.Url=Url;},{"punycode/":78,"qs":80}],114:[function(require,module,exports){module.exports={"Name":"Retold Remote","MainViewportViewIdentifier":"ContentEditor-Layout","AutoSolveAfterInitialize":true,"AutoRenderMainViewportViewAfterInitialize":false,"AutoRenderViewsAfterInitialize":false};},{}],115:[function(require,module,exports){const libContentEditorApplication=require('retold-content-system').PictContentEditor;const libPictSectionFileBrowser=require('pict-section-filebrowser');// Providers
8542
9049
  const libProviderRetoldRemote=require('./providers/Pict-Provider-RetoldRemote.js');const libProviderGalleryNavigation=require('./providers/Pict-Provider-GalleryNavigation.js');const libProviderGalleryFilterSort=require('./providers/Pict-Provider-GalleryFilterSort.js');const libProviderRetoldRemoteIcons=require('./providers/Pict-Provider-RetoldRemoteIcons.js');const libProviderRetoldRemoteTheme=require('./providers/Pict-Provider-RetoldRemoteTheme.js');// Views (replace parent views)
8543
9050
  const libViewLayout=require('./views/PictView-Remote-Layout.js');const libViewTopBar=require('./views/PictView-Remote-TopBar.js');const libViewSettingsPanel=require('./views/PictView-Remote-SettingsPanel.js');// Views (new)
8544
- const libViewGallery=require('./views/PictView-Remote-Gallery.js');const libViewMediaViewer=require('./views/PictView-Remote-MediaViewer.js');const libViewImageViewer=require('./views/PictView-Remote-ImageViewer.js');const libViewVideoExplorer=require('./views/PictView-Remote-VideoExplorer.js');const libViewAudioExplorer=require('./views/PictView-Remote-AudioExplorer.js');// Application configuration
9051
+ const libViewGallery=require('./views/PictView-Remote-Gallery.js');const libViewMediaViewer=require('./views/PictView-Remote-MediaViewer.js');const libViewImageViewer=require('./views/PictView-Remote-ImageViewer.js');const libViewVideoExplorer=require('./views/PictView-Remote-VideoExplorer.js');const libViewAudioExplorer=require('./views/PictView-Remote-AudioExplorer.js');const libViewVLCSetup=require('./views/PictView-Remote-VLCSetup.js');// Application configuration
8545
9052
  const _DefaultConfiguration=require('./Pict-Application-RetoldRemote-Configuration.json');/**
8546
9053
  * Retold Remote Application
8547
9054
  *
@@ -8552,7 +9059,7 @@ const _DefaultConfiguration=require('./Pict-Application-RetoldRemote-Configurati
8552
9059
  */class RetoldRemoteApplication extends libContentEditorApplication{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},_DefaultConfiguration,pOptions);super(pFable,tmpOptions,pServiceHash);// Replace parent views with media-focused versions.
8553
9060
  // Re-registering with the same ViewIdentifier replaces the parent's view.
8554
9061
  this.pict.addView('ContentEditor-Layout',libViewLayout.default_configuration,libViewLayout);this.pict.addView('ContentEditor-TopBar',libViewTopBar.default_configuration,libViewTopBar);// Add new views
8555
- this.pict.addView('RetoldRemote-Gallery',libViewGallery.default_configuration,libViewGallery);this.pict.addView('RetoldRemote-MediaViewer',libViewMediaViewer.default_configuration,libViewMediaViewer);this.pict.addView('RetoldRemote-ImageViewer',libViewImageViewer.default_configuration,libViewImageViewer);this.pict.addView('RetoldRemote-SettingsPanel',libViewSettingsPanel.default_configuration,libViewSettingsPanel);this.pict.addView('RetoldRemote-VideoExplorer',libViewVideoExplorer.default_configuration,libViewVideoExplorer);this.pict.addView('RetoldRemote-AudioExplorer',libViewAudioExplorer.default_configuration,libViewAudioExplorer);// Add new providers
9062
+ this.pict.addView('RetoldRemote-Gallery',libViewGallery.default_configuration,libViewGallery);this.pict.addView('RetoldRemote-MediaViewer',libViewMediaViewer.default_configuration,libViewMediaViewer);this.pict.addView('RetoldRemote-ImageViewer',libViewImageViewer.default_configuration,libViewImageViewer);this.pict.addView('RetoldRemote-SettingsPanel',libViewSettingsPanel.default_configuration,libViewSettingsPanel);this.pict.addView('RetoldRemote-VideoExplorer',libViewVideoExplorer.default_configuration,libViewVideoExplorer);this.pict.addView('RetoldRemote-AudioExplorer',libViewAudioExplorer.default_configuration,libViewAudioExplorer);this.pict.addView('RetoldRemote-VLCSetup',libViewVLCSetup.default_configuration,libViewVLCSetup);// Add new providers
8556
9063
  this.pict.addProvider('RetoldRemote-Provider',libProviderRetoldRemote.default_configuration,libProviderRetoldRemote);this.pict.addProvider('RetoldRemote-GalleryNavigation',libProviderGalleryNavigation.default_configuration,libProviderGalleryNavigation);this.pict.addProvider('RetoldRemote-GalleryFilterSort',libProviderGalleryFilterSort.default_configuration,libProviderGalleryFilterSort);this.pict.addProvider('RetoldRemote-Icons',libProviderRetoldRemoteIcons.default_configuration,libProviderRetoldRemoteIcons);this.pict.addProvider('RetoldRemote-Theme',libProviderRetoldRemoteTheme.default_configuration,libProviderRetoldRemoteTheme);}onAfterInitializeAsync(fCallback){// Expose pict on window for inline onclick handlers
8557
9064
  if(typeof window!=='undefined'){window.pict=this.pict;}// Initialize RetoldRemote-specific state
8558
9065
  this.pict.AppData.RetoldRemote={ActiveMode:'gallery',// 'gallery' or 'viewer'
@@ -8562,6 +9069,7 @@ ThumbnailSize:'medium',// 'small', 'medium', 'large'
8562
9069
  RawFileList:[],// Unfiltered server response
8563
9070
  GalleryItems:[],// Filtered+sorted file list (single source of truth)
8564
9071
  GalleryCursorIndex:0,// Currently highlighted item
9072
+ FolderCursorHistory:{},// Map of folder path -> last cursor index
8565
9073
  GalleryFilter:'all',// 'all', 'images', 'video', 'audio', 'documents'
8566
9074
  SearchQuery:'',SearchCaseSensitive:false,SearchRegex:false,ServerCapabilities:{},// From /api/media/capabilities
8567
9075
  FolderSummary:null,// From /api/media/folder-summary
@@ -8610,6 +9118,14 @@ let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewe
8610
9118
  let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(pFilePath,'document');}}else{// Default: use media viewer
8611
9119
  let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(pFilePath,tmpMediaType);}}// Update the topbar
8612
9120
  let tmpTopBar=this.pict.views['ContentEditor-TopBar'];if(tmpTopBar){tmpTopBar.updateInfo();}}/**
9121
+ * Navigate to a file with an explicit media type override, bypassing
9122
+ * extension-based detection.
9123
+ *
9124
+ * @param {string} pFilePath - Relative file path
9125
+ * @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
9126
+ */navigateToFileAs(pFilePath,pMediaType){if(!pFilePath){return;}let tmpRemote=this.pict.AppData.RetoldRemote;// Update the hash
9127
+ let tmpFragProvider=this.pict.providers['RetoldRemote-Provider'];let tmpFragId=tmpFragProvider?tmpFragProvider.getFragmentIdentifier(pFilePath):pFilePath;window.location.hash='#/view/'+tmpFragId;// Update parent state for compatibility
9128
+ this.pict.AppData.ContentEditor.CurrentFile=pFilePath;this.pict.AppData.ContentEditor.ActiveEditor='binary';let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(pFilePath,pMediaType);}let tmpTopBar=this.pict.views['ContentEditor-TopBar'];if(tmpTopBar){tmpTopBar.updateInfo();}}/**
8613
9129
  * Override loadFileList to also populate the gallery and fetch folder summary.
8614
9130
  */loadFileList(pPath,fCallback){let tmpCallback=typeof fCallback==='function'?fCallback:typeof pPath==='function'?pPath:()=>{};let tmpSelf=this;let tmpPath=typeof pPath==='string'?pPath:null;if(tmpPath===null&&this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation){tmpPath=this.pict.AppData.PictFileBrowser.CurrentLocation;}let tmpURL='/api/filebrowser/list';if(tmpPath&&tmpPath.length>0){let tmpMediaProvider=tmpSelf.pict.providers['RetoldRemote-Provider'];if(tmpMediaProvider){tmpURL+='?path='+tmpMediaProvider._getPathParam(tmpPath);}else{tmpURL+='?path='+encodeURIComponent(tmpPath);}}fetch(tmpURL).then(pResponse=>{// Capture the folder hash header before parsing JSON
8615
9131
  let tmpFolderHash=pResponse.headers.get('X-Retold-Folder-Hash');if(tmpFolderHash&&tmpPath){let tmpHashProvider=tmpSelf.pict.providers['RetoldRemote-Provider'];if(tmpHashProvider){tmpHashProvider.registerHash(tmpPath,tmpFolderHash);}}return pResponse.json();}).then(pFileList=>{tmpSelf.pict.AppData.PictFileBrowser=tmpSelf.pict.AppData.PictFileBrowser||{};tmpSelf.pict.AppData.PictFileBrowser.FileList=pFileList||[];tmpSelf.pict.AppData.PictFileBrowser.CurrentLocation=typeof pPath==='string'?pPath:tmpSelf.pict.AppData.PictFileBrowser.CurrentLocation||'';// Register hashes from file list entries (when hashed filenames is on)
@@ -8619,7 +9135,7 @@ let tmpRemote=tmpSelf.pict.AppData.RetoldRemote;tmpRemote.RawFileList=pFileList|
8619
9135
  tmpRemote.FilterState={MediaType:tmpRemote.FilterState.MediaType,Extensions:[],SizeMin:null,SizeMax:null,DateModifiedAfter:null,DateModifiedBefore:null,DateCreatedAfter:null,DateCreatedBefore:null};// Show the gallery, hide the viewer
8620
9136
  let tmpGalleryContainer=document.getElementById('RetoldRemote-Gallery-Container');let tmpViewerContainer=document.getElementById('RetoldRemote-Viewer-Container');if(tmpGalleryContainer)tmpGalleryContainer.style.display='';if(tmpViewerContainer)tmpViewerContainer.style.display='none';// Run the filter+sort pipeline (sets GalleryItems, resets cursor, renders gallery)
8621
9137
  let tmpFilterSort=tmpSelf.pict.providers['RetoldRemote-GalleryFilterSort'];if(tmpFilterSort){tmpFilterSort.applyFilterSort();}else{// Fallback if provider not ready
8622
- tmpRemote.GalleryItems=pFileList||[];tmpRemote.GalleryCursorIndex=0;let tmpGalleryView=tmpSelf.pict.views['RetoldRemote-Gallery'];if(tmpGalleryView){tmpGalleryView.renderGallery();}}// Update the hash (use hashed identifier when available)
9138
+ tmpRemote.GalleryItems=pFileList||[];let tmpSavedIndex=tmpRemote.FolderCursorHistory&&tmpRemote.FolderCursorHistory[tmpSelf.pict.AppData.PictFileBrowser.CurrentLocation||''];tmpRemote.GalleryCursorIndex=typeof tmpSavedIndex==='number'&&tmpSavedIndex<(pFileList||[]).length?tmpSavedIndex:0;let tmpGalleryView=tmpSelf.pict.views['RetoldRemote-Gallery'];if(tmpGalleryView){tmpGalleryView.renderGallery();}}// Update the hash (use hashed identifier when available)
8623
9139
  let tmpCurrentPath=tmpSelf.pict.AppData.PictFileBrowser.CurrentLocation||'';let tmpBrowseFragProvider=tmpSelf.pict.providers['RetoldRemote-Provider'];let tmpBrowseFragId=tmpBrowseFragProvider&&tmpCurrentPath?tmpBrowseFragProvider.getFragmentIdentifier(tmpCurrentPath):tmpCurrentPath;window.location.hash=tmpBrowseFragId?'#/browse/'+tmpBrowseFragId:'#/browse/';// Fetch folder summary for topbar info (skip for archive paths — they are not filesystem directories)
8624
9140
  let tmpMediaProvider=tmpSelf.pict.providers['RetoldRemote-Provider'];let tmpIsArchivePath=/\.(zip|7z|rar|tar|tgz|cbz|cbr|tar\.gz|tar\.bz2|tar\.xz)(\/|$)/i.test(tmpCurrentPath);if(tmpMediaProvider&&!tmpIsArchivePath){tmpMediaProvider.fetchFolderSummary(tmpCurrentPath,(pError,pSummary)=>{if(!pError&&pSummary){tmpRemote.FolderSummary=pSummary;let tmpTopBar=tmpSelf.pict.views['ContentEditor-TopBar'];if(tmpTopBar){tmpTopBar.updateLocation();tmpTopBar.updateInfo();}}});}else{let tmpTopBar=tmpSelf.pict.views['ContentEditor-TopBar'];if(tmpTopBar){tmpTopBar.updateLocation();tmpTopBar.updateInfo();}}return tmpCallback();}).catch(pError=>{tmpSelf.log.error(`Failed to load file list: ${pError.message}`);return tmpCallback();});}/**
8625
9141
  * Override resolveHash to handle gallery and viewer routes.
@@ -8631,11 +9147,11 @@ let tmpFilePath=tmpFragProvider?tmpFragProvider.resolveFragmentIdentifier(tmpRaw
8631
9147
  }}/**
8632
9148
  * Load RetoldRemote settings from localStorage.
8633
9149
  */_loadRemoteSettings(){try{let tmpStored=localStorage.getItem('retold-remote-settings');if(tmpStored){let tmpSettings=JSON.parse(tmpStored);let tmpRemote=this.pict.AppData.RetoldRemote;if(tmpSettings.Theme)tmpRemote.Theme=tmpSettings.Theme;if(tmpSettings.ViewMode)tmpRemote.ViewMode=tmpSettings.ViewMode;if(tmpSettings.ThumbnailSize)tmpRemote.ThumbnailSize=tmpSettings.ThumbnailSize;if(tmpSettings.GalleryFilter){tmpRemote.GalleryFilter=tmpSettings.GalleryFilter;tmpRemote.FilterState.MediaType=tmpSettings.GalleryFilter;}if(typeof tmpSettings.ShowHiddenFiles==='boolean')tmpRemote.ShowHiddenFiles=tmpSettings.ShowHiddenFiles;if(typeof tmpSettings.DistractionFreeShowNav==='boolean')tmpRemote.DistractionFreeShowNav=tmpSettings.DistractionFreeShowNav;if(tmpSettings.ImageFitMode)tmpRemote.ImageFitMode=tmpSettings.ImageFitMode;if(typeof tmpSettings.SidebarCollapsed==='boolean')tmpRemote.SidebarCollapsed=tmpSettings.SidebarCollapsed;if(tmpSettings.SidebarWidth)tmpRemote.SidebarWidth=tmpSettings.SidebarWidth;if(tmpSettings.SortField)tmpRemote.SortField=tmpSettings.SortField;if(tmpSettings.SortDirection)tmpRemote.SortDirection=tmpSettings.SortDirection;if(Array.isArray(tmpSettings.FilterPresets))tmpRemote.FilterPresets=tmpSettings.FilterPresets;if(typeof tmpSettings.FilterPanelOpen==='boolean')tmpRemote.FilterPanelOpen=tmpSettings.FilterPanelOpen;if(typeof tmpSettings.AutoplayVideo==='boolean')tmpRemote.AutoplayVideo=tmpSettings.AutoplayVideo;if(typeof tmpSettings.AutoplayAudio==='boolean')tmpRemote.AutoplayAudio=tmpSettings.AutoplayAudio;}}catch(pError){// localStorage may not be available
8634
- }}}module.exports=RetoldRemoteApplication;},{"./Pict-Application-RetoldRemote-Configuration.json":114,"./providers/Pict-Provider-GalleryFilterSort.js":117,"./providers/Pict-Provider-GalleryNavigation.js":118,"./providers/Pict-Provider-RetoldRemote.js":119,"./providers/Pict-Provider-RetoldRemoteIcons.js":120,"./providers/Pict-Provider-RetoldRemoteTheme.js":121,"./views/PictView-Remote-AudioExplorer.js":122,"./views/PictView-Remote-Gallery.js":123,"./views/PictView-Remote-ImageViewer.js":124,"./views/PictView-Remote-Layout.js":125,"./views/PictView-Remote-MediaViewer.js":126,"./views/PictView-Remote-SettingsPanel.js":127,"./views/PictView-Remote-TopBar.js":128,"./views/PictView-Remote-VideoExplorer.js":129,"pict-section-filebrowser":54,"retold-content-system":100}],116:[function(require,module,exports){/**
9150
+ }}}module.exports=RetoldRemoteApplication;},{"./Pict-Application-RetoldRemote-Configuration.json":114,"./providers/Pict-Provider-GalleryFilterSort.js":117,"./providers/Pict-Provider-GalleryNavigation.js":118,"./providers/Pict-Provider-RetoldRemote.js":119,"./providers/Pict-Provider-RetoldRemoteIcons.js":120,"./providers/Pict-Provider-RetoldRemoteTheme.js":121,"./views/PictView-Remote-AudioExplorer.js":122,"./views/PictView-Remote-Gallery.js":123,"./views/PictView-Remote-ImageViewer.js":124,"./views/PictView-Remote-Layout.js":125,"./views/PictView-Remote-MediaViewer.js":126,"./views/PictView-Remote-SettingsPanel.js":127,"./views/PictView-Remote-TopBar.js":128,"./views/PictView-Remote-VLCSetup.js":129,"./views/PictView-Remote-VideoExplorer.js":130,"pict-section-filebrowser":51,"retold-content-system":100}],116:[function(require,module,exports){/**
8635
9151
  * Retold Remote -- Browser Bundle Entry
8636
9152
  *
8637
9153
  * Exports the RetoldRemote application class for browser consumption.
8638
- */module.exports={RetoldRemoteApplication:require('./Pict-Application-RetoldRemote.js')};if(typeof window!=='undefined'){window.RetoldRemoteApplication=module.exports.RetoldRemoteApplication;}},{"./Pict-Application-RetoldRemote.js":115}],117:[function(require,module,exports){const libPictProvider=require('pict-provider');const _ImageExtensions={'png':true,'jpg':true,'jpeg':true,'gif':true,'webp':true,'svg':true,'bmp':true,'ico':true,'avif':true,'tiff':true,'tif':true};const _VideoExtensions={'mp4':true,'webm':true,'mov':true,'mkv':true,'avi':true,'wmv':true,'flv':true,'m4v':true};const _AudioExtensions={'mp3':true,'wav':true,'ogg':true,'flac':true,'aac':true,'m4a':true,'wma':true};const _DocumentExtensions={'pdf':true,'epub':true,'mobi':true};const _DefaultProviderConfiguration={ProviderIdentifier:'RetoldRemote-GalleryFilterSort',AutoInitialize:true,AutoInitializeOrdinal:0,AutoSolveWithApp:false};class GalleryFilterSortProvider extends libPictProvider{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);}// ──────────────────────────────────────────────
9154
+ */module.exports={RetoldRemoteApplication:require('./Pict-Application-RetoldRemote.js')};if(typeof window!=='undefined'){window.RetoldRemoteApplication=module.exports.RetoldRemoteApplication;}},{"./Pict-Application-RetoldRemote.js":115}],117:[function(require,module,exports){const libPictProvider=require('pict-provider');const _ImageExtensions={'png':true,'jpg':true,'jpeg':true,'gif':true,'webp':true,'svg':true,'bmp':true,'ico':true,'avif':true,'tiff':true,'tif':true};const _VideoExtensions={'mp4':true,'webm':true,'mov':true,'mkv':true,'avi':true,'wmv':true,'flv':true,'m4v':true,'ogv':true,'mpg':true,'mpeg':true,'mpe':true,'mpv':true,'m2v':true,'ts':true,'mts':true,'m2ts':true,'vob':true,'3gp':true,'3g2':true,'f4v':true,'rm':true,'rmvb':true,'divx':true,'asf':true,'mxf':true,'dv':true,'nsv':true,'nuv':true,'y4m':true,'wtv':true,'swf':true,'dat':true};const _AudioExtensions={'mp3':true,'wav':true,'ogg':true,'flac':true,'aac':true,'m4a':true,'wma':true};const _DocumentExtensions={'pdf':true,'epub':true,'mobi':true};const _DefaultProviderConfiguration={ProviderIdentifier:'RetoldRemote-GalleryFilterSort',AutoInitialize:true,AutoInitializeOrdinal:0,AutoSolveWithApp:false};class GalleryFilterSortProvider extends libPictProvider{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);}// ──────────────────────────────────────────────
8639
9155
  // Pipeline
8640
9156
  // ──────────────────────────────────────────────
8641
9157
  /**
@@ -8649,7 +9165,8 @@ tmpItems=this._applyExtensionFilter(tmpItems,tmpFilterState.Extensions||[]);// 4
8649
9165
  tmpItems=this._applySizeFilter(tmpItems,tmpFilterState.SizeMin,tmpFilterState.SizeMax);// 5. Date range filter
8650
9166
  tmpItems=this._applyDateFilter(tmpItems,tmpFilterState);// 6. Sort
8651
9167
  tmpItems=this._sortItems(tmpItems,tmpRemote.SortField||'folder-first',tmpRemote.SortDirection||'asc');// Write result
8652
- tmpRemote.GalleryItems=tmpItems;tmpRemote.GalleryCursorIndex=0;// Re-render gallery
9168
+ tmpRemote.GalleryItems=tmpItems;// Restore cursor position if we have a saved one for this folder
9169
+ let tmpCurrentLocation=this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation||'';let tmpSavedIndex=tmpRemote.FolderCursorHistory&&tmpRemote.FolderCursorHistory[tmpCurrentLocation];if(typeof tmpSavedIndex==='number'&&tmpSavedIndex<tmpItems.length){tmpRemote.GalleryCursorIndex=tmpSavedIndex;}else{tmpRemote.GalleryCursorIndex=0;}// Re-render gallery
8653
9170
  let tmpGalleryView=this.pict.views['RetoldRemote-Gallery'];if(tmpGalleryView){tmpGalleryView.renderGallery();}}// ──────────────────────────────────────────────
8654
9171
  // Filter stages
8655
9172
  // ──────────────────────────────────────────────
@@ -8746,7 +9263,7 @@ if(pEvent.key==='Escape'&&pEvent.target.id==='RetoldRemote-Gallery-Search'){pEve
8746
9263
  if(pEvent.target.tagName==='INPUT'||pEvent.target.tagName==='TEXTAREA'||pEvent.target.isContentEditable){return;}// If the help panel is visible, Escape closes it
8747
9264
  if(tmpSelf._helpPanelVisible&&pEvent.key==='Escape'){pEvent.preventDefault();tmpSelf._toggleHelpPanel();return;}let tmpRemote=tmpSelf.pict.AppData.RetoldRemote;let tmpActiveMode=tmpRemote.ActiveMode;if(tmpActiveMode==='gallery'&&tmpSelf._sidebarFocused){tmpSelf._handleSidebarKey(pEvent);}else if(tmpActiveMode==='gallery'){tmpSelf._handleGalleryKey(pEvent);}else if(tmpActiveMode==='video-explorer'){tmpSelf._handleVideoExplorerKey(pEvent);}else if(tmpActiveMode==='audio-explorer'){tmpSelf._handleAudioExplorerKey(pEvent);}else if(tmpActiveMode==='viewer'){tmpSelf._handleViewerKey(pEvent);}};document.addEventListener('keydown',this._keydownHandler);this._keydownBound=true;}/**
8748
9265
  * Handle keyboard events in gallery mode.
8749
- */_handleGalleryKey(pEvent){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpItems=tmpRemote.GalleryItems||[];let tmpIndex=tmpRemote.GalleryCursorIndex||0;switch(pEvent.key){case'ArrowRight':pEvent.preventDefault();this.moveCursor(Math.min(tmpIndex+1,tmpItems.length-1));break;case'ArrowLeft':pEvent.preventDefault();this.moveCursor(Math.max(tmpIndex-1,0));break;case'ArrowDown':pEvent.preventDefault();this.moveCursor(Math.min(tmpIndex+this._columnsPerRow,tmpItems.length-1));break;case'ArrowUp':pEvent.preventDefault();this.moveCursor(Math.max(tmpIndex-this._columnsPerRow,0));break;case'Enter':pEvent.preventDefault();this.openCurrent();break;case'Escape':pEvent.preventDefault();this.navigateUp();break;case'g':pEvent.preventDefault();this._toggleViewMode();break;case'x':pEvent.preventDefault();this._clearAllFilters();break;case'Home':pEvent.preventDefault();this.moveCursor(0);break;case'End':pEvent.preventDefault();this.moveCursor(tmpItems.length-1);break;case'f':pEvent.preventDefault();{// Ensure the filter bar is visible first
9266
+ */_handleGalleryKey(pEvent){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpItems=tmpRemote.GalleryItems||[];let tmpIndex=tmpRemote.GalleryCursorIndex||0;switch(pEvent.key){case'ArrowRight':pEvent.preventDefault();this.moveCursor(Math.min(tmpIndex+1,tmpItems.length-1));break;case'ArrowLeft':pEvent.preventDefault();this.moveCursor(Math.max(tmpIndex-1,0));break;case'ArrowDown':pEvent.preventDefault();this.moveCursor(Math.min(tmpIndex+this._columnsPerRow,tmpItems.length-1));break;case'ArrowUp':pEvent.preventDefault();this.moveCursor(Math.max(tmpIndex-this._columnsPerRow,0));break;case'Enter':pEvent.preventDefault();this.openCurrent();break;case'Escape':pEvent.preventDefault();this.navigateUp();break;case'g':pEvent.preventDefault();this._toggleViewMode();break;case'x':pEvent.preventDefault();this._clearAllFilters();break;case'Home':pEvent.preventDefault();this.moveCursor(0);break;case'End':pEvent.preventDefault();this.moveCursor(tmpItems.length-1);break;case'1':pEvent.preventDefault();this.openCurrentAs('image');break;case'2':pEvent.preventDefault();this.openCurrentAs('video');break;case'3':pEvent.preventDefault();this.openCurrentAs('audio');break;case'4':pEvent.preventDefault();this.openCurrentAs('text');break;case'f':pEvent.preventDefault();{// Ensure the filter bar is visible first
8750
9267
  this._showFilterBar();let tmpGalleryView=this.pict.views['RetoldRemote-Gallery'];if(tmpGalleryView){tmpGalleryView.toggleFilterPanel();}}break;case's':pEvent.preventDefault();{// Ensure the filter bar is visible first
8751
9268
  this._showFilterBar();setTimeout(()=>{let tmpSortSelect=document.getElementById('RetoldRemote-Gallery-Sort');if(tmpSortSelect){tmpSortSelect.focus();}},50);}break;case'c':pEvent.preventDefault();this._toggleSettingsPanel();break;case'd':pEvent.preventDefault();this._toggleDistractionFree();break;}}/**
8752
9269
  * Handle keyboard events when the sidebar file list has focus.
@@ -8766,7 +9283,8 @@ let tmpRows=document.querySelectorAll('#Pict-FileBrowser-DetailRows .pict-fb-det
8766
9283
  if(this._sidebarCursorIndex<tmpRows.length){tmpRows[this._sidebarCursorIndex].classList.remove('sidebar-focused');}this._sidebarCursorIndex=pIndex;// Apply new highlight and scroll into view
8767
9284
  if(pIndex<tmpRows.length){tmpRows[pIndex].classList.add('sidebar-focused');tmpRows[pIndex].scrollIntoView({block:'nearest',behavior:'smooth'});}}/**
8768
9285
  * Handle keyboard events in viewer mode.
8769
- */_handleViewerKey(pEvent){switch(pEvent.key){case'Escape':pEvent.preventDefault();this.closeViewer();break;case'ArrowRight':case'j':pEvent.preventDefault();this.nextFile();break;case'ArrowLeft':case'k':pEvent.preventDefault();this.prevFile();break;case'f':pEvent.preventDefault();this._toggleFullscreen();break;case'i':pEvent.preventDefault();this._toggleFileInfo();break;case' ':pEvent.preventDefault();this._togglePlayPause();break;case'+':case'=':pEvent.preventDefault();this._zoomIn();break;case'-':pEvent.preventDefault();this._zoomOut();break;case'0':pEvent.preventDefault();this._zoomReset();break;case'z':pEvent.preventDefault();this._cycleFitMode();break;case'Enter':pEvent.preventDefault();this._openWithVLC();break;case'd':pEvent.preventDefault();this._toggleDistractionFree();break;}}/**
9286
+ */_handleViewerKey(pEvent){let tmpRemote=this.pict.AppData.RetoldRemote;// Video action menu mode — intercept keys for menu options
9287
+ if(tmpRemote.VideoMenuActive&&tmpRemote.CurrentViewerMediaType==='video'){switch(pEvent.key){case'Escape':pEvent.preventDefault();this.closeViewer();return;case'ArrowRight':case'j':pEvent.preventDefault();this.nextFile();return;case'ArrowLeft':case'k':pEvent.preventDefault();this.prevFile();return;case'e':pEvent.preventDefault();let tmpVEX=this.pict.views['RetoldRemote-VideoExplorer'];if(tmpVEX){tmpVEX.showExplorer(tmpRemote.CurrentViewerFile);}return;case' ':case'Enter':pEvent.preventDefault();let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.playVideo();}return;case't':pEvent.preventDefault();let tmpMediaViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpMediaViewer){tmpMediaViewer.loadVideoMenuFrame();}return;case'v':pEvent.preventDefault();this._streamWithVLC();return;}return;}switch(pEvent.key){case'Escape':pEvent.preventDefault();this.closeViewer();break;case'ArrowRight':case'j':pEvent.preventDefault();this.nextFile();break;case'ArrowLeft':case'k':pEvent.preventDefault();this.prevFile();break;case'f':pEvent.preventDefault();this._toggleFullscreen();break;case'i':pEvent.preventDefault();this._toggleFileInfo();break;case' ':pEvent.preventDefault();this._togglePlayPause();break;case'+':case'=':pEvent.preventDefault();this._zoomIn();break;case'-':pEvent.preventDefault();this._zoomOut();break;case'0':pEvent.preventDefault();this._zoomReset();break;case'z':pEvent.preventDefault();this._cycleFitMode();break;case'Enter':pEvent.preventDefault();this._streamWithVLC();break;case'v':pEvent.preventDefault();this._streamWithVLC();break;case'd':pEvent.preventDefault();this._toggleDistractionFree();break;case'1':pEvent.preventDefault();this.switchViewerType('image');break;case'2':pEvent.preventDefault();this.switchViewerType('video');break;case'3':pEvent.preventDefault();this.switchViewerType('audio');break;case'4':pEvent.preventDefault();this.switchViewerType('text');break;}}/**
8770
9288
  * Handle keyboard events in video explorer mode.
8771
9289
  */_handleVideoExplorerKey(pEvent){switch(pEvent.key){case'Escape':pEvent.preventDefault();let tmpVEX=this.pict.views['RetoldRemote-VideoExplorer'];if(tmpVEX){tmpVEX.goBack();}break;}}/**
8772
9290
  * Handle keyboard events in audio explorer mode.
@@ -8778,11 +9296,22 @@ if(pIndex<tmpRows.length){tmpRows[pIndex].classList.add('sidebar-focused');tmpRo
8778
9296
  let tmpOldTile=document.querySelector(`.retold-remote-tile[data-index="${tmpOldIndex}"], .retold-remote-list-row[data-index="${tmpOldIndex}"]`);let tmpNewTile=document.querySelector(`.retold-remote-tile[data-index="${pNewIndex}"], .retold-remote-list-row[data-index="${pNewIndex}"]`);if(tmpOldTile){tmpOldTile.classList.remove('selected');}if(tmpNewTile){tmpNewTile.classList.add('selected');// Scroll the tile into view if needed
8779
9297
  tmpNewTile.scrollIntoView({block:'nearest',behavior:'smooth'});}}/**
8780
9298
  * Open the currently selected gallery item.
8781
- */openCurrent(){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpItems=tmpRemote.GalleryItems||[];let tmpIndex=tmpRemote.GalleryCursorIndex||0;if(tmpIndex>=tmpItems.length){return;}let tmpItem=tmpItems[tmpIndex];if(tmpItem.Type==='folder'||tmpItem.Type==='archive'){// Navigate into the folder or archive
9299
+ */openCurrent(){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpItems=tmpRemote.GalleryItems||[];let tmpIndex=tmpRemote.GalleryCursorIndex||0;if(tmpIndex>=tmpItems.length){return;}let tmpItem=tmpItems[tmpIndex];if(tmpItem.Type==='folder'||tmpItem.Type==='archive'){// Remember cursor position in the current folder before navigating away
9300
+ let tmpCurrentLocation=this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation||'';tmpRemote.FolderCursorHistory[tmpCurrentLocation]=tmpIndex;// Navigate into the folder or archive
8782
9301
  let tmpApp=this.pict.PictApplication;if(tmpApp&&tmpApp.loadFileList){tmpApp.loadFileList(tmpItem.Path);}}else{// Open the file in the viewer
8783
9302
  let tmpApp=this.pict.PictApplication;if(tmpApp&&tmpApp.navigateToFile){tmpApp.navigateToFile(tmpItem.Path);}}}/**
9303
+ * Open the currently selected gallery item, forcing a specific media type
9304
+ * regardless of its file extension.
9305
+ *
9306
+ * @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
9307
+ */openCurrentAs(pMediaType){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpItems=tmpRemote.GalleryItems||[];let tmpIndex=tmpRemote.GalleryCursorIndex||0;if(tmpIndex>=tmpItems.length){return;}let tmpItem=tmpItems[tmpIndex];if(tmpItem.Type==='folder'||tmpItem.Type==='archive'){return;}let tmpApp=this.pict.PictApplication;if(tmpApp&&tmpApp.navigateToFileAs){tmpApp.navigateToFileAs(tmpItem.Path,pMediaType);}}/**
9308
+ * Re-open the currently viewed file with a different media type.
9309
+ *
9310
+ * @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
9311
+ */switchViewerType(pMediaType){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpFilePath=tmpRemote.CurrentViewerFile;if(!tmpFilePath){return;}let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(tmpFilePath,pMediaType);}}/**
8784
9312
  * Navigate up one directory level.
8785
- */navigateUp(){let tmpCurrentLocation=this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation||'';if(!tmpCurrentLocation){return;}let tmpParent=tmpCurrentLocation.indexOf('/')>=0?tmpCurrentLocation.replace(/\/[^/]+\/?$/,''):'';let tmpApp=this.pict.PictApplication;if(tmpApp&&tmpApp.loadFileList){tmpApp.loadFileList(tmpParent);}}/**
9313
+ */navigateUp(){let tmpCurrentLocation=this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation||'';if(!tmpCurrentLocation){return;}// Remember cursor position in the current folder before navigating away
9314
+ let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.FolderCursorHistory[tmpCurrentLocation]=tmpRemote.GalleryCursorIndex||0;let tmpParent=tmpCurrentLocation.indexOf('/')>=0?tmpCurrentLocation.replace(/\/[^/]+\/?$/,''):'';let tmpApp=this.pict.PictApplication;if(tmpApp&&tmpApp.loadFileList){tmpApp.loadFileList(tmpParent);}}/**
8786
9315
  * Close the viewer and return to gallery mode.
8787
9316
  */closeViewer(){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='gallery';let tmpGalleryContainer=document.getElementById('RetoldRemote-Gallery-Container');let tmpViewerContainer=document.getElementById('RetoldRemote-Viewer-Container');if(tmpGalleryContainer)tmpGalleryContainer.style.display='';if(tmpViewerContainer)tmpViewerContainer.style.display='none';// Restore the hash to the browse route (use hashed identifier when available)
8788
9317
  let tmpCurrentLocation=this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation||'';let tmpFragProvider=this.pict.providers['RetoldRemote-Provider'];let tmpFragId=tmpFragProvider&&tmpCurrentLocation?tmpFragProvider.getFragmentIdentifier(tmpCurrentLocation):tmpCurrentLocation;window.location.hash=tmpFragId?'#/browse/'+tmpFragId:'#/browse/';// Re-render gallery to ensure cursor is visible
@@ -8859,6 +9388,12 @@ if(tmpRemote.CurrentViewerMediaType!=='video'){return;}// Check if VLC is availa
8859
9388
  let tmpCapabilities=tmpRemote.ServerCapabilities||{};if(!tmpCapabilities.vlc){return;}let tmpFilePath=tmpRemote.CurrentViewerFile;if(!tmpFilePath){return;}// Show a brief toast
8860
9389
  this._showToast('Opening in VLC...');// POST to the server to open the file
8861
9390
  fetch('/api/media/open',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:tmpFilePath})}).then(pResponse=>{return pResponse.json();}).then(pData=>{if(!pData.Success){this._showToast('Failed to open: '+(pData.Error||'Unknown error'));}}).catch(pError=>{this._showToast('Failed to open: '+pError.message);});}/**
9391
+ * Stream the current media file to VLC on the client device via vlc:// protocol link.
9392
+ */_streamWithVLC(){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpMediaType=tmpRemote.CurrentViewerMediaType;if(tmpMediaType!=='video'&&tmpMediaType!=='audio'){return;}let tmpFilePath=tmpRemote.CurrentViewerFile;if(!tmpFilePath){return;}let tmpProvider=this.pict.providers['RetoldRemote-Provider'];let tmpContentPath=tmpProvider?tmpProvider.getContentURL(tmpFilePath):'/content/'+encodeURIComponent(tmpFilePath);let tmpStreamURL=window.location.origin+tmpContentPath;// On Windows, VLC's native handler expects the raw URL.
9393
+ // On macOS/Linux our custom handlers URL-decode, so we encode.
9394
+ let tmpIsWindows=/Windows/.test(navigator.userAgent);let tmpVLCURL=tmpIsWindows?'vlc://'+tmpStreamURL:'vlc://'+encodeURIComponent(tmpStreamURL);this._showToast('Opening VLC...');// Use a temporary anchor element to trigger the protocol handler
9395
+ // without navigating the current page away
9396
+ let tmpLink=document.createElement('a');tmpLink.href=tmpVLCURL;tmpLink.style.display='none';document.body.appendChild(tmpLink);tmpLink.click();document.body.removeChild(tmpLink);}/**
8862
9397
  * Show a brief toast notification in the viewer.
8863
9398
  *
8864
9399
  * @param {string} pMessage - Text to display
@@ -9084,7 +9619,7 @@ return this.getIcon('file',tmpSize);}/**
9084
9619
  * @returns {Object} Extension-to-icon name map
9085
9620
  */getExtensionMap(){return Object.assign({},this._extensionMap);}/**
9086
9621
  * Inject CSS classes for icon sizing into the pict CSSMap.
9087
- */injectCSS(){if(this._cssInjected){return;}if(this.pict&&this.pict.CSSMap){this.pict.CSSMap.addCSS('RetoldRemoteIcons','.retold-remote-icon { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; }\n'+'.retold-remote-icon svg { display: block; }\n'+'.retold-remote-icon-sm svg { width: 16px; height: 16px; }\n'+'.retold-remote-icon-md svg { width: 48px; height: 48px; }\n'+'.retold-remote-icon-lg svg { width: 64px; height: 64px; }\n'+'.retold-remote-icon-xl svg { width: 96px; height: 96px; }\n');this._cssInjected=true;}}onAfterInitialize(){this.injectCSS();return super.onAfterInitialize();}}module.exports=RetoldRemoteIconProvider;module.exports.default_configuration=_DefaultProviderConfiguration;module.exports.DefaultColors=_DefaultColors;},{"pict-provider":46,"pict-section-filebrowser/source/providers/Pict-Provider-FileBrowserIcons.js":56}],121:[function(require,module,exports){const libPictProvider=require('pict-provider');const _ProviderConfiguration={ProviderIdentifier:'RetoldRemote-Theme',AutoInitialize:true,AutoInitializeOrdinal:0};/**
9622
+ */injectCSS(){if(this._cssInjected){return;}if(this.pict&&this.pict.CSSMap){this.pict.CSSMap.addCSS('RetoldRemoteIcons','.retold-remote-icon { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; }\n'+'.retold-remote-icon svg { display: block; }\n'+'.retold-remote-icon-sm svg { width: 16px; height: 16px; }\n'+'.retold-remote-icon-md svg { width: 48px; height: 48px; }\n'+'.retold-remote-icon-lg svg { width: 64px; height: 64px; }\n'+'.retold-remote-icon-xl svg { width: 96px; height: 96px; }\n');this._cssInjected=true;}}onAfterInitialize(){this.injectCSS();return super.onAfterInitialize();}}module.exports=RetoldRemoteIconProvider;module.exports.default_configuration=_DefaultProviderConfiguration;module.exports.DefaultColors=_DefaultColors;},{"pict-provider":46,"pict-section-filebrowser/source/providers/Pict-Provider-FileBrowserIcons.js":53}],121:[function(require,module,exports){const libPictProvider=require('pict-provider');const _ProviderConfiguration={ProviderIdentifier:'RetoldRemote-Theme',AutoInitialize:true,AutoInitializeOrdinal:0};/**
9088
9623
  * Theme provider for retold-remote.
9089
9624
  *
9090
9625
  * Manages 15 themes (5 grey-only + 10 fun) via CSS custom properties.
@@ -10735,10 +11270,89 @@ tmpHandle.addEventListener('dblclick',function(pEvent){pEvent.preventDefault();t
10735
11270
  font-family: inherit;
10736
11271
  white-space: nowrap;
10737
11272
  }
10738
- .retold-remote-vlc-btn:hover
11273
+ .retold-remote-vlc-btn:hover
11274
+ {
11275
+ background: var(--retold-accent);
11276
+ color: var(--retold-bg-primary);
11277
+ }
11278
+ /* Video action menu */
11279
+ .retold-remote-video-action-menu
11280
+ {
11281
+ display: flex;
11282
+ flex-direction: column;
11283
+ align-items: center;
11284
+ justify-content: center;
11285
+ gap: 12px;
11286
+ width: 100%;
11287
+ height: 100%;
11288
+ }
11289
+ .retold-remote-video-action-menu-title
11290
+ {
11291
+ font-size: 0.85rem;
11292
+ color: var(--retold-text-secondary);
11293
+ margin-bottom: 4px;
11294
+ text-align: center;
11295
+ overflow: hidden;
11296
+ text-overflow: ellipsis;
11297
+ max-width: 80%;
11298
+ }
11299
+ .retold-remote-video-action-thumb-wrap
11300
+ {
11301
+ margin-bottom: 4px;
11302
+ text-align: center;
11303
+ }
11304
+ .retold-remote-video-action-thumb-wrap img
11305
+ {
11306
+ max-width: 640px;
11307
+ max-height: 360px;
11308
+ border-radius: 6px;
11309
+ border: 1px solid var(--retold-border);
11310
+ object-fit: contain;
11311
+ background: var(--retold-bg-primary);
11312
+ }
11313
+ .retold-remote-video-action-thumb-wrap .retold-remote-video-action-thumb-loading
11314
+ {
11315
+ color: var(--retold-text-dim);
11316
+ font-size: 0.78rem;
11317
+ font-style: italic;
11318
+ padding: 8px;
11319
+ }
11320
+ .retold-remote-video-action-btn
11321
+ {
11322
+ display: flex;
11323
+ align-items: center;
11324
+ gap: 12px;
11325
+ padding: 12px 24px;
11326
+ min-width: 280px;
11327
+ border: 1px solid var(--retold-border);
11328
+ border-radius: 6px;
11329
+ background: var(--retold-bg-secondary);
11330
+ color: var(--retold-text-secondary);
11331
+ font-size: 0.85rem;
11332
+ cursor: pointer;
11333
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
11334
+ font-family: inherit;
11335
+ text-align: left;
11336
+ }
11337
+ .retold-remote-video-action-btn:hover,
11338
+ .retold-remote-video-action-btn.selected
11339
+ {
11340
+ border-color: var(--retold-accent);
11341
+ color: var(--retold-text-primary);
11342
+ background: var(--retold-bg-tertiary);
11343
+ }
11344
+ .retold-remote-video-action-key
10739
11345
  {
10740
- background: var(--retold-accent);
10741
- color: var(--retold-bg-primary);
11346
+ display: inline-block;
11347
+ padding: 2px 8px;
11348
+ border: 1px solid var(--retold-border);
11349
+ border-radius: 3px;
11350
+ background: var(--retold-bg-primary);
11351
+ color: var(--retold-text-dim);
11352
+ font-size: 0.72rem;
11353
+ font-family: var(--retold-font-mono, monospace);
11354
+ min-width: 24px;
11355
+ text-align: center;
10742
11356
  }
10743
11357
  /* Ebook reader */
10744
11358
  .retold-remote-ebook-wrap
@@ -10867,7 +11481,7 @@ tmpHandle.addEventListener('dblclick',function(pEvent){pEvent.preventDefault();t
10867
11481
  *
10868
11482
  * @param {string} pFilePath - Relative file path
10869
11483
  * @param {string} pMediaType - 'image', 'video', 'audio', 'document', or 'other'
10870
- */showMedia(pFilePath,pMediaType){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='viewer';tmpRemote.CurrentViewerFile=pFilePath;tmpRemote.CurrentViewerMediaType=pMediaType;// Show viewer, hide gallery
11484
+ */showMedia(pFilePath,pMediaType){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='viewer';tmpRemote.CurrentViewerFile=pFilePath;tmpRemote.CurrentViewerMediaType=pMediaType;tmpRemote.VideoMenuActive=pMediaType==='video';// Show viewer, hide gallery
10871
11485
  let tmpGalleryContainer=document.getElementById('RetoldRemote-Gallery-Container');let tmpViewerContainer=document.getElementById('RetoldRemote-Viewer-Container');if(tmpGalleryContainer)tmpGalleryContainer.style.display='none';if(tmpViewerContainer)tmpViewerContainer.style.display='block';let tmpFileName=pFilePath.replace(/^.*\//,'');let tmpProvider=this.pict.providers['RetoldRemote-Provider'];let tmpContentURL=tmpProvider?tmpProvider.getContentURL(pFilePath):'/content/'+encodeURIComponent(pFilePath);// Build the viewer HTML
10872
11486
  let tmpHTML='<div class="retold-remote-viewer">';// Header with nav
10873
11487
  tmpHTML+='<div class="retold-remote-viewer-header">';tmpHTML+='<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].closeViewer()" title="Back (Esc)">&larr; Back</button>';tmpHTML+='<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].prevFile()" title="Previous (k)">&lsaquo; Prev</button>';tmpHTML+='<div class="retold-remote-viewer-title">'+this._escapeHTML(tmpFileName)+'</div>';tmpHTML+='<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].nextFile()" title="Next (j)">Next &rsaquo;</button>';tmpHTML+='<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFileInfo()" title="Info (i)">&#9432;</button>';tmpHTML+='<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFullscreen()" title="Fullscreen (f)">&#9634;</button>';tmpHTML+='</div>';// Body with media content
@@ -10878,13 +11492,27 @@ if(tmpViewerContainer){tmpViewerContainer.innerHTML=tmpHTML;}// Fetch and popula
10878
11492
  this._loadFileInfo(pFilePath);// Fetch text content and initialize code viewer
10879
11493
  if(pMediaType==='text'){this._loadCodeViewer(tmpContentURL,pFilePath);}// Load ebook viewer for epub/mobi
10880
11494
  if(pMediaType==='document'){let tmpExt=pFilePath.replace(/^.*\./,'').toLowerCase();if(tmpExt==='epub'||tmpExt==='mobi'){this._loadEbookViewer(tmpContentURL,pFilePath);}}// Update topbar
10881
- let tmpTopBar=this.pict.views['ContentEditor-TopBar'];if(tmpTopBar){tmpTopBar.updateInfo();}}_buildImageHTML(pURL,pFileName){return'<img src="'+pURL+'" alt="'+this._escapeHTML(pFileName)+'" '+'style="max-width: 100%; max-height: 100%; object-fit: contain; cursor: zoom-in;" '+'id="RetoldRemote-ImageViewer-Img" '+'onload="pict.views[\'RetoldRemote-ImageViewer\'].initImage()" '+'onclick="pict.views[\'RetoldRemote-ImageViewer\'].toggleZoom()">';}_buildVideoHTML(pURL,pFileName){let tmpHTML='<div class="retold-remote-video-wrap">';let tmpAutoplayVideo=this.pict.AppData.RetoldRemote.AutoplayVideo?' autoplay':'';tmpHTML+='<video controls'+tmpAutoplayVideo+' preload="metadata" '+'id="RetoldRemote-VideoPlayer">'+'<source src="'+pURL+'">'+'Your browser does not support the video tag.'+'</video>';// Stats bar below the video
10882
- tmpHTML+='<div class="retold-remote-video-stats" id="RetoldRemote-VideoStats">';tmpHTML+='<span class="retold-remote-video-stat-label">Loading info...</span>';// Explore Video button (only when ffmpeg is available)
10883
- let tmpCapabilities=this.pict.AppData.RetoldRemote.ServerCapabilities||{};if(tmpCapabilities.ffmpeg){tmpHTML+='<button class="retold-remote-explore-btn" '+'onclick="pict.views[\'RetoldRemote-VideoExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '+'title="Explore frames from this video">'+'&#128270; Explore Video'+'</button>';}// VLC button (only shown when VLC capability is available)
10884
- if(tmpCapabilities.vlc){tmpHTML+='<button class="retold-remote-vlc-btn" '+'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._openWithVLC()" '+'title="Open with VLC (Enter)">'+'&#9654; Open '+this._escapeHTML(pFileName)+' with VLC'+'</button>';}tmpHTML+='</div>';// end stats
11495
+ let tmpTopBar=this.pict.views['ContentEditor-TopBar'];if(tmpTopBar){tmpTopBar.updateInfo();}}_buildImageHTML(pURL,pFileName){return'<img src="'+pURL+'" alt="'+this._escapeHTML(pFileName)+'" '+'style="max-width: 100%; max-height: 100%; object-fit: contain; cursor: zoom-in;" '+'id="RetoldRemote-ImageViewer-Img" '+'onload="pict.views[\'RetoldRemote-ImageViewer\'].initImage()" '+'onclick="pict.views[\'RetoldRemote-ImageViewer\'].toggleZoom()">';}_buildVideoHTML(pURL,pFileName){let tmpCapabilities=this.pict.AppData.RetoldRemote.ServerCapabilities||{};let tmpRemote=this.pict.AppData.RetoldRemote;let tmpFilePath=tmpRemote.CurrentViewerFile;// Build the action menu (shown by default instead of the player)
11496
+ let tmpHTML='<div class="retold-remote-video-action-menu" id="RetoldRemote-VideoActionMenu">';tmpHTML+='<div class="retold-remote-video-action-menu-title">'+this._escapeHTML(pFileName)+'</div>';// Frame preview container (loaded on demand via t key or automatically)
11497
+ if(tmpCapabilities.ffmpeg){tmpHTML+='<div id="RetoldRemote-VideoActionThumb" class="retold-remote-video-action-thumb-wrap"></div>';// Kick off frame extraction automatically
11498
+ setTimeout(()=>{this.loadVideoMenuFrame();},0);}// Explore option (e)
11499
+ if(tmpCapabilities.ffmpeg){tmpHTML+='<button class="retold-remote-video-action-btn" '+'onclick="pict.views[\'RetoldRemote-VideoExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '+'title="Explore frames from this video">'+'<span class="retold-remote-video-action-key">e</span>'+'Explore Video Frames'+'</button>';}// Play option (space/enter)
11500
+ tmpHTML+='<button class="retold-remote-video-action-btn selected" '+'onclick="pict.views[\'RetoldRemote-MediaViewer\'].playVideo()" '+'title="Play video in browser">'+'<span class="retold-remote-video-action-key">Space</span>'+'Play in Browser'+'</button>';// Thumbnail option (t)
11501
+ if(tmpCapabilities.ffmpeg){tmpHTML+='<button class="retold-remote-video-action-btn" '+'onclick="pict.views[\'RetoldRemote-MediaViewer\'].loadVideoMenuFrame()" '+'title="Extract a frame from the midpoint of this video">'+'<span class="retold-remote-video-action-key">t</span>'+'Thumbnail'+'</button>';}// VLC streaming option (v) — always available, streams from server to client VLC
11502
+ tmpHTML+='<button class="retold-remote-video-action-btn" '+'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '+'title="Stream to VLC on this device">'+'<span class="retold-remote-video-action-key">v</span>'+'Stream with VLC'+'</button>';tmpHTML+='</div>';return tmpHTML;}/**
11503
+ * Launch the in-browser video player (from the video action menu).
11504
+ */playVideo(){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpFilePath=tmpRemote.CurrentViewerFile;if(!tmpFilePath)return;let tmpFileName=tmpFilePath.replace(/^.*\//,'');let tmpProvider=this.pict.providers['RetoldRemote-Provider'];let tmpContentURL=tmpProvider?tmpProvider.getContentURL(tmpFilePath):'/content/'+encodeURIComponent(tmpFilePath);let tmpCapabilities=tmpRemote.ServerCapabilities||{};let tmpHTML='<div class="retold-remote-video-wrap">';let tmpAutoplayVideo=tmpRemote.AutoplayVideo?' autoplay':'';tmpHTML+='<video controls'+tmpAutoplayVideo+' preload="metadata" '+'id="RetoldRemote-VideoPlayer">'+'<source src="'+tmpContentURL+'">'+'Your browser does not support the video tag.'+'</video>';// Stats bar below the video
11505
+ tmpHTML+='<div class="retold-remote-video-stats" id="RetoldRemote-VideoStats">';tmpHTML+='<span class="retold-remote-video-stat-label">Loading info...</span>';if(tmpCapabilities.ffmpeg){tmpHTML+='<button class="retold-remote-explore-btn" '+'onclick="pict.views[\'RetoldRemote-VideoExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '+'title="Explore frames from this video">'+'Explore Video'+'</button>';}tmpHTML+='<button class="retold-remote-vlc-btn" '+'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '+'title="Stream to VLC on this device">'+'Stream with VLC'+'</button>';tmpHTML+='</div>';// end stats
10885
11506
  tmpHTML+='</div>';// end wrap
10886
- return tmpHTML;}_buildAudioHTML(pURL,pFileName){let tmpIconProvider=this.pict.providers['RetoldRemote-Icons'];let tmpIconHTML=tmpIconProvider?'<span class="retold-remote-icon retold-remote-icon-lg">'+tmpIconProvider.getIcon('music-note',64)+'</span>':'&#127925;';let tmpHTML='<div style="text-align: center; padding: 40px;">'+'<div style="margin-bottom: 24px;">'+tmpIconHTML+'</div>'+'<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">'+this._escapeHTML(pFileName)+'</div>'+'<audio controls'+(this.pict.AppData.RetoldRemote.AutoplayAudio?' autoplay':'')+' preload="metadata" id="RetoldRemote-AudioPlayer" style="width: 100%; max-width: 500px;">'+'<source src="'+pURL+'">'+'Your browser does not support the audio tag.'+'</audio>';// Explore Audio button (available when ffprobe is present)
10887
- let tmpCapabilities=this.pict.AppData.RetoldRemote.ServerCapabilities||{};if(tmpCapabilities.ffprobe||tmpCapabilities.ffmpeg){tmpHTML+='<div style="margin-top: 20px;">'+'<button class="retold-remote-explore-btn" '+'onclick="pict.views[\'RetoldRemote-AudioExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '+'title="Explore waveform and extract segments from this audio">'+'&#128202; Explore Audio'+'</button>'+'</div>';}tmpHTML+='</div>';return tmpHTML;}_buildDocumentHTML(pURL,pFileName,pFilePath){let tmpExtension=pFilePath.replace(/^.*\./,'').toLowerCase();if(tmpExtension==='pdf'){return'<iframe src="'+pURL+'" '+'style="width: 100%; height: 100%; border: none;">'+'</iframe>';}if(tmpExtension==='epub'||tmpExtension==='mobi'){return this._buildEbookHTML(pURL,pFileName,pFilePath);}// For other document types, show a download link
11507
+ // Replace the action menu with the player
11508
+ let tmpMenu=document.getElementById('RetoldRemote-VideoActionMenu');if(tmpMenu){tmpMenu.outerHTML=tmpHTML;}// Mark that we are now in player mode (not menu mode)
11509
+ tmpRemote.VideoMenuActive=false;}/**
11510
+ * Extract and display a single full-resolution frame from the midpoint of the current video.
11511
+ */loadVideoMenuFrame(){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpFilePath=tmpRemote.CurrentViewerFile;if(!tmpFilePath)return;let tmpThumbWrap=document.getElementById('RetoldRemote-VideoActionThumb');if(!tmpThumbWrap)return;tmpThumbWrap.innerHTML='<div class="retold-remote-video-action-thumb-loading">Extracting frame...</div>';let tmpProvider=this.pict.providers['RetoldRemote-Provider'];let tmpPathParam=tmpProvider?tmpProvider._getPathParam(tmpFilePath):encodeURIComponent(tmpFilePath);fetch('/api/media/video-frames?path='+tmpPathParam+'&count=1').then(pResponse=>pResponse.json()).then(pData=>{// Verify we are still on the same file and still in the menu
11512
+ if(tmpRemote.CurrentViewerFile!==tmpFilePath)return;let tmpWrap=document.getElementById('RetoldRemote-VideoActionThumb');if(!tmpWrap)return;if(pData&&pData.Frames&&pData.Frames.length>0){let tmpFrame=pData.Frames[0];let tmpFrameURL='/api/media/video-frame/'+pData.CacheKey+'/'+tmpFrame.Filename;tmpWrap.innerHTML='<img src="'+tmpFrameURL+'" '+'alt="'+this._escapeHTML(tmpFilePath.replace(/^.*\//,''))+'" '+'onerror="this.parentNode.innerHTML=\'\'">';}else{tmpWrap.innerHTML='';}}).catch(()=>{let tmpWrap=document.getElementById('RetoldRemote-VideoActionThumb');if(tmpWrap)tmpWrap.innerHTML='';});}_buildAudioHTML(pURL,pFileName){let tmpIconProvider=this.pict.providers['RetoldRemote-Icons'];let tmpIconHTML=tmpIconProvider?'<span class="retold-remote-icon retold-remote-icon-lg">'+tmpIconProvider.getIcon('music-note',64)+'</span>':'&#127925;';let tmpHTML='<div style="text-align: center; padding: 40px;">'+'<div style="margin-bottom: 24px;">'+tmpIconHTML+'</div>'+'<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">'+this._escapeHTML(pFileName)+'</div>'+'<audio controls'+(this.pict.AppData.RetoldRemote.AutoplayAudio?' autoplay':'')+' preload="metadata" id="RetoldRemote-AudioPlayer" style="width: 100%; max-width: 500px;">'+'<source src="'+pURL+'">'+'Your browser does not support the audio tag.'+'</audio>';// Action buttons below the player
11513
+ tmpHTML+='<div style="margin-top: 20px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">';// Explore Audio button (available when ffprobe is present)
11514
+ let tmpCapabilities=this.pict.AppData.RetoldRemote.ServerCapabilities||{};if(tmpCapabilities.ffprobe||tmpCapabilities.ffmpeg){tmpHTML+='<button class="retold-remote-explore-btn" '+'onclick="pict.views[\'RetoldRemote-AudioExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '+'title="Explore waveform and extract segments from this audio">'+'Explore Audio'+'</button>';}// Stream with VLC
11515
+ tmpHTML+='<button class="retold-remote-vlc-btn" '+'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '+'title="Stream to VLC on this device (v)">'+'Stream with VLC'+'</button>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildDocumentHTML(pURL,pFileName,pFilePath){let tmpExtension=pFilePath.replace(/^.*\./,'').toLowerCase();if(tmpExtension==='pdf'){return'<iframe src="'+pURL+'" '+'style="width: 100%; height: 100%; border: none;">'+'</iframe>';}if(tmpExtension==='epub'||tmpExtension==='mobi'){return this._buildEbookHTML(pURL,pFileName,pFilePath);}// For other document types, show a download link
10888
11516
  let tmpIconProvider=this.pict.providers['RetoldRemote-Icons'];let tmpDocIconHTML=tmpIconProvider?'<span class="retold-remote-icon retold-remote-icon-lg">'+tmpIconProvider.getIcon('document-large',64)+'</span>':'&#128196;';return'<div style="text-align: center; padding: 40px;">'+'<div style="margin-bottom: 24px;">'+tmpDocIconHTML+'</div>'+'<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">'+this._escapeHTML(pFileName)+'</div>'+'<a href="'+pURL+'" target="_blank" style="color: var(--retold-accent); font-size: 0.9rem;">Open in new tab</a>'+'</div>';}_buildTextHTML(pURL,pFileName,pFilePath){return'<div class="retold-remote-code-viewer-container" id="RetoldRemote-CodeViewer-Container">'+'<div class="retold-remote-code-viewer-loading">Loading...</div>'+'</div>';}/**
10889
11517
  * Map a file extension to a pict-section-code highlight language.
10890
11518
  *
@@ -11016,6 +11644,64 @@ let tmpExploreBtn=tmpStatsBar.querySelector('.retold-remote-explore-btn');let tm
11016
11644
  {
11017
11645
  color: var(--retold-danger-muted);
11018
11646
  }
11647
+ .retold-remote-settings-vlc-btn
11648
+ {
11649
+ display: block;
11650
+ width: 100%;
11651
+ padding: 8px 12px;
11652
+ border: 1px solid var(--retold-border);
11653
+ border-radius: 4px;
11654
+ background: var(--retold-bg-secondary);
11655
+ color: var(--retold-text-secondary);
11656
+ font-size: 0.75rem;
11657
+ font-family: inherit;
11658
+ cursor: pointer;
11659
+ text-align: left;
11660
+ transition: background 0.15s, color 0.15s;
11661
+ }
11662
+ .retold-remote-settings-vlc-btn:hover
11663
+ {
11664
+ background: var(--retold-bg-hover);
11665
+ color: var(--retold-text-primary);
11666
+ }
11667
+ .retold-remote-settings-shortcut-group
11668
+ {
11669
+ margin-bottom: 10px;
11670
+ }
11671
+ .retold-remote-settings-shortcut-group-title
11672
+ {
11673
+ font-size: 0.68rem;
11674
+ font-weight: 600;
11675
+ color: var(--retold-text-muted);
11676
+ margin-bottom: 4px;
11677
+ padding-bottom: 2px;
11678
+ border-bottom: 1px solid var(--retold-border);
11679
+ }
11680
+ .retold-remote-settings-shortcut-row
11681
+ {
11682
+ display: flex;
11683
+ justify-content: space-between;
11684
+ align-items: center;
11685
+ padding: 2px 0;
11686
+ }
11687
+ .retold-remote-settings-shortcut-desc
11688
+ {
11689
+ color: var(--retold-text-dim);
11690
+ font-size: 0.72rem;
11691
+ }
11692
+ .retold-remote-settings-shortcut-key
11693
+ {
11694
+ display: inline-block;
11695
+ padding: 1px 6px;
11696
+ border: 1px solid var(--retold-border);
11697
+ border-radius: 3px;
11698
+ background: var(--retold-bg-primary);
11699
+ color: var(--retold-accent);
11700
+ font-size: 0.68rem;
11701
+ font-family: var(--retold-font-mono, monospace);
11702
+ min-width: 18px;
11703
+ text-align: center;
11704
+ }
11019
11705
  `};class RetoldRemoteSettingsPanelView extends libPictView{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);}onAfterRender(){super.onAfterRender();this._renderSettingsContent();}_renderSettingsContent(){let tmpContainer=document.getElementById('RetoldRemote-Settings-Container');if(!tmpContainer){return;}let tmpRemote=this.pict.AppData.RetoldRemote;let tmpCapabilities=tmpRemote.ServerCapabilities||{};let tmpHTML='<div class="retold-remote-settings">';// Appearance section (theme dropdown)
11020
11706
  tmpHTML+='<div class="retold-remote-settings-section">';tmpHTML+='<div class="retold-remote-settings-section-title">Appearance</div>';tmpHTML+='<div class="retold-remote-settings-row">';tmpHTML+='<span class="retold-remote-settings-label">Theme</span>';tmpHTML+='<select class="retold-remote-settings-select" onchange="pict.views[\'RetoldRemote-SettingsPanel\'].changeTheme(this.value)">';let tmpThemeProvider=this.pict.providers['RetoldRemote-Theme'];if(tmpThemeProvider){let tmpThemes=tmpThemeProvider.getThemeList();let tmpCurrentTheme=tmpThemeProvider.getCurrentTheme();let tmpCurrentCategory='';for(let i=0;i<tmpThemes.length;i++){let tmpTheme=tmpThemes[i];if(tmpTheme.category!==tmpCurrentCategory){if(tmpCurrentCategory){tmpHTML+='</optgroup>';}tmpHTML+='<optgroup label="'+tmpTheme.category+'">';tmpCurrentCategory=tmpTheme.category;}tmpHTML+='<option value="'+tmpTheme.key+'"'+(tmpTheme.key===tmpCurrentTheme?' selected':'')+'>'+tmpTheme.name+'</option>';}if(tmpCurrentCategory){tmpHTML+='</optgroup>';}}tmpHTML+='</select>';tmpHTML+='</div>';tmpHTML+='</div>';// end appearance section
11021
11707
  // Gallery section
@@ -11029,10 +11715,11 @@ tmpHTML+='<div class="retold-remote-settings-row">';tmpHTML+='<span class="retol
11029
11715
  // Server capabilities
11030
11716
  tmpHTML+='<div class="retold-remote-settings-section">';tmpHTML+='<div class="retold-remote-settings-section-title">Server Capabilities</div>';tmpHTML+='<div class="retold-remote-settings-capabilities">';let tmpTools=[{key:'sharp',label:'Sharp (image thumbnails)'},{key:'imagemagick',label:'ImageMagick (image fallback)'},{key:'ffmpeg',label:'ffmpeg (video thumbnails)'},{key:'ffprobe',label:'ffprobe (media metadata)'}];for(let i=0;i<tmpTools.length;i++){let tmpTool=tmpTools[i];let tmpAvailable=tmpCapabilities[tmpTool.key];tmpHTML+='<div class="retold-remote-settings-cap-row">';tmpHTML+='<span class="retold-remote-settings-cap-label">'+tmpTool.label+'</span>';tmpHTML+='<span class="'+(tmpAvailable?'retold-remote-settings-cap-yes':'retold-remote-settings-cap-no')+'">'+(tmpAvailable?'Available':'Not found')+'</span>';tmpHTML+='</div>';}// Hashed filenames status
11031
11717
  tmpHTML+='<div class="retold-remote-settings-cap-row" style="margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--retold-border);">';tmpHTML+='<span class="retold-remote-settings-cap-label">Hashed filenames</span>';tmpHTML+='<span class="'+(tmpRemote.HashedFilenames?'retold-remote-settings-cap-yes':'retold-remote-settings-cap-no')+'">'+(tmpRemote.HashedFilenames?'Enabled':'Disabled')+'</span>';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='</div>';// end capabilities section
11032
- // Keyboard shortcuts
11033
- tmpHTML+='<div class="retold-remote-settings-section">';tmpHTML+='<div class="retold-remote-settings-section-title">Keyboard Shortcuts</div>';let tmpShortcuts=[{key:'Arrow keys',desc:'Navigate gallery'},{key:'Enter',desc:'Open item'},{key:'Escape',desc:'Back / close'},{key:'j / k',desc:'Next / prev in viewer'},{key:'f',desc:'Fullscreen'},{key:'i',desc:'File info'},{key:'Space',desc:'Play / pause'},{key:'+ / -',desc:'Zoom in / out'},{key:'0',desc:'Reset zoom'},{key:'g',desc:'Toggle grid / list'},{key:'/',desc:'Focus search'}];for(let i=0;i<tmpShortcuts.length;i++){tmpHTML+='<div class="retold-remote-settings-cap-row">';tmpHTML+='<span class="retold-remote-settings-cap-label">'+tmpShortcuts[i].desc+'</span>';tmpHTML+='<span style="color: var(--retold-accent); font-family: var(--retold-font-mono, monospace);">'+tmpShortcuts[i].key+'</span>';tmpHTML+='</div>';}tmpHTML+='</div>';// end shortcuts section
11718
+ // VLC Setup
11719
+ tmpHTML+='<div class="retold-remote-settings-section">';tmpHTML+='<div class="retold-remote-settings-section-title">VLC Streaming</div>';tmpHTML+='<button class="retold-remote-settings-vlc-btn" onclick="pict.views[\'RetoldRemote-VLCSetup\'].openModal()">';tmpHTML+='VLC Protocol Setup';tmpHTML+='</button>';tmpHTML+='</div>';// Keyboard shortcuts
11720
+ tmpHTML+='<div class="retold-remote-settings-section">';tmpHTML+='<div class="retold-remote-settings-section-title">Keyboard Shortcuts</div>';tmpHTML+=this._buildShortcutGroup('Global',[{key:'F1',desc:'Help panel'},{key:'F9',desc:'Focus sidebar'},{key:'/',desc:'Search / filter bar'},{key:'Esc',desc:'Close overlay / back'}]);tmpHTML+=this._buildShortcutGroup('Gallery',[{key:'\u2190 \u2191 \u2192 \u2193',desc:'Navigate items'},{key:'Enter',desc:'Open item'},{key:'1 2 3 4',desc:'Open as image / video / audio / text'},{key:'Esc',desc:'Go up one folder'},{key:'Home',desc:'Jump to first item'},{key:'End',desc:'Jump to last item'},{key:'g',desc:'Toggle grid / list'},{key:'f',desc:'Advanced filter panel'},{key:'s',desc:'Focus sort dropdown'},{key:'x',desc:'Clear all filters'},{key:'c',desc:'Settings panel'},{key:'d',desc:'Distraction-free mode'}]);tmpHTML+=this._buildShortcutGroup('Sidebar (F9)',[{key:'\u2191 \u2193',desc:'Navigate file list'},{key:'Home',desc:'Jump to first'},{key:'End',desc:'Jump to last'},{key:'Enter',desc:'Open item'},{key:'Esc',desc:'Return to gallery'}]);tmpHTML+=this._buildShortcutGroup('Media Viewer',[{key:'Esc',desc:'Back to gallery'},{key:'\u2192 j',desc:'Next file'},{key:'\u2190 k',desc:'Previous file'},{key:'1 2 3 4',desc:'View as image / video / audio / text'},{key:'Space',desc:'Play / pause'},{key:'f',desc:'Fullscreen'},{key:'i',desc:'File info overlay'},{key:'v',desc:'Stream with VLC'},{key:'+ -',desc:'Zoom in / out'},{key:'0',desc:'Reset zoom'},{key:'z',desc:'Cycle fit mode'},{key:'d',desc:'Distraction-free mode'}]);tmpHTML+=this._buildShortcutGroup('Video Menu',[{key:'Space',desc:'Play in browser'},{key:'Enter',desc:'Play in browser'},{key:'e',desc:'Explore video frames'},{key:'t',desc:'Extract thumbnail'},{key:'v',desc:'Stream with VLC'},{key:'\u2192 j',desc:'Next file'},{key:'\u2190 k',desc:'Previous file'},{key:'Esc',desc:'Back to gallery'}]);tmpHTML+=this._buildShortcutGroup('Video Explorer',[{key:'Esc',desc:'Back'}]);tmpHTML+=this._buildShortcutGroup('Audio Explorer',[{key:'Space',desc:'Play selection'},{key:'+ -',desc:'Zoom in / out'},{key:'0',desc:'Zoom to fit'},{key:'z',desc:'Zoom to selection'},{key:'Esc',desc:'Clear selection / back'}]);tmpHTML+='</div>';// end shortcuts section
11034
11721
  tmpHTML+='</div>';// end settings
11035
- tmpContainer.innerHTML=tmpHTML;}changeTheme(pThemeKey){let tmpThemeProvider=this.pict.providers['RetoldRemote-Theme'];if(tmpThemeProvider){tmpThemeProvider.applyTheme(pThemeKey);this.pict.PictApplication.saveSettings();// Re-render settings to update dropdown selection
11722
+ tmpContainer.innerHTML=tmpHTML;}_buildShortcutGroup(pTitle,pShortcuts){let tmpHTML='<div class="retold-remote-settings-shortcut-group">';tmpHTML+='<div class="retold-remote-settings-shortcut-group-title">'+pTitle+'</div>';for(let i=0;i<pShortcuts.length;i++){tmpHTML+='<div class="retold-remote-settings-shortcut-row">';tmpHTML+='<span class="retold-remote-settings-shortcut-desc">'+pShortcuts[i].desc+'</span>';tmpHTML+='<span class="retold-remote-settings-shortcut-key">'+pShortcuts[i].key+'</span>';tmpHTML+='</div>';}tmpHTML+='</div>';return tmpHTML;}changeTheme(pThemeKey){let tmpThemeProvider=this.pict.providers['RetoldRemote-Theme'];if(tmpThemeProvider){tmpThemeProvider.applyTheme(pThemeKey);this.pict.PictApplication.saveSettings();// Re-render settings to update dropdown selection
11036
11723
  this._renderSettingsContent();}}changeSetting(pKey,pValue){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote[pKey]=pValue;this.pict.PictApplication.saveSettings();// Re-render gallery if visible
11037
11724
  if(tmpRemote.ActiveMode==='gallery'){let tmpGalleryView=this.pict.views['RetoldRemote-Gallery'];if(tmpGalleryView){tmpGalleryView.renderGallery();}}}toggleHiddenFiles(pChecked){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ShowHiddenFiles=pChecked;this.pict.PictApplication.saveSettings();this.pict.PictApplication.syncHiddenFilesSetting(()=>{this.pict.PictApplication.loadFileList();});}toggleAutoplay(pKey,pChecked){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote[pKey]=pChecked;this.pict.PictApplication.saveSettings();}toggleDistractionFreeNav(pChecked){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.DistractionFreeShowNav=pChecked;this.pict.PictApplication.saveSettings();// If currently in distraction-free mode, apply immediately
11038
11725
  if(tmpRemote._distractionFreeMode){let tmpViewerHeader=document.querySelector('.retold-remote-viewer-header');if(tmpViewerHeader){tmpViewerHeader.style.display=pChecked?'':'none';}}}}RetoldRemoteSettingsPanelView.default_configuration=_ViewConfiguration;module.exports=RetoldRemoteSettingsPanelView;},{"pict-view":76}],128:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={ViewIdentifier:"ContentEditor-TopBar",DefaultRenderable:"RetoldRemote-TopBar",DefaultDestinationAddress:"#ContentEditor-TopBar-Container",AutoRender:false,CSS:/*css*/`
@@ -11179,7 +11866,259 @@ tmpBtn.classList.add('filter-active');tmpBtn.innerHTML='&#9683;<span class="reto
11179
11866
  tmpBtn.classList.add('filter-bar-open');tmpBtn.innerHTML='&#9698;';tmpBtn.title='Hide filter bar (/)';}else{// Default: no filters, bar hidden
11180
11867
  tmpBtn.innerHTML='&#9698;';tmpBtn.title='Toggle filter bar (/)';}}/**
11181
11868
  * Update the info display with folder summary.
11182
- */updateInfo(){let tmpInfoEl=document.getElementById('RetoldRemote-TopBar-Info');if(!tmpInfoEl){return;}let tmpRemote=this.pict.AppData.RetoldRemote;let tmpSummary=tmpRemote.FolderSummary;if(tmpRemote.ActiveMode==='viewer'){let tmpItems=tmpRemote.GalleryItems||[];let tmpIndex=tmpRemote.GalleryCursorIndex||0;let tmpItem=tmpItems[tmpIndex];if(tmpItem){tmpInfoEl.textContent=tmpItem.Name;}return;}if(!tmpSummary){tmpInfoEl.textContent='';return;}let tmpParts=[];if(tmpSummary.Folders>0)tmpParts.push(tmpSummary.Folders+' folders');if(tmpSummary.Images>0)tmpParts.push(tmpSummary.Images+' images');if(tmpSummary.Videos>0)tmpParts.push(tmpSummary.Videos+' videos');if(tmpSummary.Audio>0)tmpParts.push(tmpSummary.Audio+' audio');if(tmpSummary.Documents>0)tmpParts.push(tmpSummary.Documents+' docs');if(tmpSummary.Other>0)tmpParts.push(tmpSummary.Other+' other');tmpInfoEl.textContent=tmpParts.join(' \u00b7 ');}}RetoldRemoteTopBarView.default_configuration=_ViewConfiguration;module.exports=RetoldRemoteTopBarView;},{"pict-view":76}],129:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={ViewIdentifier:"RetoldRemote-VideoExplorer",DefaultRenderable:"RetoldRemote-VideoExplorer",DefaultDestinationAddress:"#RetoldRemote-Viewer-Container",AutoRender:false,CSS:/*css*/`
11869
+ */updateInfo(){let tmpInfoEl=document.getElementById('RetoldRemote-TopBar-Info');if(!tmpInfoEl){return;}let tmpRemote=this.pict.AppData.RetoldRemote;let tmpSummary=tmpRemote.FolderSummary;if(tmpRemote.ActiveMode==='viewer'){let tmpItems=tmpRemote.GalleryItems||[];let tmpIndex=tmpRemote.GalleryCursorIndex||0;let tmpItem=tmpItems[tmpIndex];if(tmpItem){tmpInfoEl.textContent=tmpItem.Name;}return;}if(!tmpSummary){tmpInfoEl.textContent='';return;}let tmpParts=[];if(tmpSummary.Folders>0)tmpParts.push(tmpSummary.Folders+' folders');if(tmpSummary.Images>0)tmpParts.push(tmpSummary.Images+' images');if(tmpSummary.Videos>0)tmpParts.push(tmpSummary.Videos+' videos');if(tmpSummary.Audio>0)tmpParts.push(tmpSummary.Audio+' audio');if(tmpSummary.Documents>0)tmpParts.push(tmpSummary.Documents+' docs');if(tmpSummary.Other>0)tmpParts.push(tmpSummary.Other+' other');tmpInfoEl.textContent=tmpParts.join(' \u00b7 ');}}RetoldRemoteTopBarView.default_configuration=_ViewConfiguration;module.exports=RetoldRemoteTopBarView;},{"pict-view":76}],129:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={ViewIdentifier:"RetoldRemote-VLCSetup",DefaultRenderable:"RetoldRemote-VLCSetup",DefaultDestinationAddress:"#ContentEditor-Application-Container",AutoRender:false,CSS:/*css*/`
11870
+ .retold-remote-vlc-modal-backdrop
11871
+ {
11872
+ position: fixed;
11873
+ top: 0;
11874
+ left: 0;
11875
+ right: 0;
11876
+ bottom: 0;
11877
+ background: rgba(0, 0, 0, 0.6);
11878
+ z-index: 9000;
11879
+ display: flex;
11880
+ align-items: center;
11881
+ justify-content: center;
11882
+ }
11883
+ .retold-remote-vlc-modal
11884
+ {
11885
+ background: var(--retold-bg-tertiary);
11886
+ border: 1px solid var(--retold-border);
11887
+ border-radius: 8px;
11888
+ width: 600px;
11889
+ max-width: 90vw;
11890
+ max-height: 85vh;
11891
+ display: flex;
11892
+ flex-direction: column;
11893
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
11894
+ }
11895
+ .retold-remote-vlc-modal-header
11896
+ {
11897
+ display: flex;
11898
+ align-items: center;
11899
+ justify-content: space-between;
11900
+ padding: 14px 18px;
11901
+ border-bottom: 1px solid var(--retold-border);
11902
+ flex-shrink: 0;
11903
+ }
11904
+ .retold-remote-vlc-modal-title
11905
+ {
11906
+ font-size: 0.85rem;
11907
+ font-weight: 700;
11908
+ color: var(--retold-text-primary);
11909
+ }
11910
+ .retold-remote-vlc-modal-close
11911
+ {
11912
+ border: none;
11913
+ background: transparent;
11914
+ color: var(--retold-text-muted);
11915
+ font-size: 1.1rem;
11916
+ cursor: pointer;
11917
+ padding: 2px 6px;
11918
+ border-radius: 3px;
11919
+ font-family: inherit;
11920
+ line-height: 1;
11921
+ }
11922
+ .retold-remote-vlc-modal-close:hover
11923
+ {
11924
+ background: var(--retold-bg-hover);
11925
+ color: var(--retold-text-primary);
11926
+ }
11927
+ .retold-remote-vlc-modal-body
11928
+ {
11929
+ flex: 1;
11930
+ overflow-y: auto;
11931
+ padding: 18px;
11932
+ }
11933
+ .retold-remote-vlc-setup-section
11934
+ {
11935
+ margin-bottom: 18px;
11936
+ }
11937
+ .retold-remote-vlc-setup-section-title
11938
+ {
11939
+ font-size: 0.7rem;
11940
+ font-weight: 700;
11941
+ text-transform: uppercase;
11942
+ letter-spacing: 0.5px;
11943
+ color: var(--retold-text-dim);
11944
+ margin-bottom: 8px;
11945
+ }
11946
+ .retold-remote-vlc-setup-desc
11947
+ {
11948
+ font-size: 0.75rem;
11949
+ color: var(--retold-text-secondary);
11950
+ line-height: 1.5;
11951
+ margin-bottom: 8px;
11952
+ }
11953
+ .retold-remote-vlc-setup-status
11954
+ {
11955
+ display: flex;
11956
+ align-items: center;
11957
+ gap: 8px;
11958
+ padding: 8px;
11959
+ border-radius: 4px;
11960
+ background: var(--retold-bg-secondary);
11961
+ margin-bottom: 12px;
11962
+ font-size: 0.75rem;
11963
+ color: var(--retold-text-secondary);
11964
+ }
11965
+ .retold-remote-vlc-setup-status-dot
11966
+ {
11967
+ width: 8px;
11968
+ height: 8px;
11969
+ border-radius: 50%;
11970
+ flex-shrink: 0;
11971
+ }
11972
+ .retold-remote-vlc-setup-status-dot.detected
11973
+ {
11974
+ background: var(--retold-accent);
11975
+ }
11976
+ .retold-remote-vlc-setup-status-dot.unknown
11977
+ {
11978
+ background: var(--retold-text-dim);
11979
+ }
11980
+ .retold-remote-vlc-setup-platform
11981
+ {
11982
+ display: none;
11983
+ }
11984
+ .retold-remote-vlc-setup-platform.active
11985
+ {
11986
+ display: block;
11987
+ }
11988
+ .retold-remote-vlc-setup-platform-tabs
11989
+ {
11990
+ display: flex;
11991
+ gap: 0;
11992
+ margin-bottom: 12px;
11993
+ border-bottom: 1px solid var(--retold-border);
11994
+ }
11995
+ .retold-remote-vlc-setup-platform-tab
11996
+ {
11997
+ padding: 6px 12px;
11998
+ border: none;
11999
+ background: transparent;
12000
+ font-size: 0.72rem;
12001
+ font-weight: 600;
12002
+ color: var(--retold-text-muted);
12003
+ cursor: pointer;
12004
+ border-bottom: 2px solid transparent;
12005
+ font-family: inherit;
12006
+ }
12007
+ .retold-remote-vlc-setup-platform-tab:hover
12008
+ {
12009
+ color: var(--retold-text-secondary);
12010
+ }
12011
+ .retold-remote-vlc-setup-platform-tab.active
12012
+ {
12013
+ color: var(--retold-accent);
12014
+ border-bottom-color: var(--retold-accent);
12015
+ }
12016
+ .retold-remote-vlc-setup-code
12017
+ {
12018
+ background: var(--retold-bg-primary);
12019
+ border: 1px solid var(--retold-border);
12020
+ border-radius: 4px;
12021
+ padding: 10px;
12022
+ font-family: "SF Mono", "Fira Code", "Consolas", monospace;
12023
+ font-size: 0.68rem;
12024
+ color: var(--retold-text-secondary);
12025
+ line-height: 1.6;
12026
+ overflow-x: auto;
12027
+ white-space: pre;
12028
+ margin-bottom: 8px;
12029
+ tab-size: 4;
12030
+ }
12031
+ .retold-remote-vlc-setup-btn
12032
+ {
12033
+ display: inline-block;
12034
+ padding: 6px 14px;
12035
+ border: 1px solid var(--retold-border);
12036
+ border-radius: 4px;
12037
+ background: var(--retold-bg-secondary);
12038
+ color: var(--retold-text-secondary);
12039
+ font-size: 0.72rem;
12040
+ font-family: inherit;
12041
+ cursor: pointer;
12042
+ transition: background 0.15s, color 0.15s;
12043
+ margin-right: 6px;
12044
+ margin-bottom: 6px;
12045
+ }
12046
+ .retold-remote-vlc-setup-btn:hover
12047
+ {
12048
+ background: var(--retold-bg-hover);
12049
+ color: var(--retold-text-primary);
12050
+ }
12051
+ .retold-remote-vlc-setup-btn.primary
12052
+ {
12053
+ background: var(--retold-accent);
12054
+ border-color: var(--retold-accent);
12055
+ color: #fff;
12056
+ }
12057
+ .retold-remote-vlc-setup-btn.primary:hover
12058
+ {
12059
+ opacity: 0.85;
12060
+ }
12061
+ .retold-remote-vlc-setup-step
12062
+ {
12063
+ display: flex;
12064
+ gap: 10px;
12065
+ margin-bottom: 10px;
12066
+ }
12067
+ .retold-remote-vlc-setup-step-num
12068
+ {
12069
+ flex-shrink: 0;
12070
+ width: 20px;
12071
+ height: 20px;
12072
+ border-radius: 50%;
12073
+ background: var(--retold-accent);
12074
+ color: #fff;
12075
+ font-size: 0.65rem;
12076
+ font-weight: 700;
12077
+ display: flex;
12078
+ align-items: center;
12079
+ justify-content: center;
12080
+ }
12081
+ .retold-remote-vlc-setup-step-content
12082
+ {
12083
+ flex: 1;
12084
+ font-size: 0.75rem;
12085
+ color: var(--retold-text-secondary);
12086
+ line-height: 1.5;
12087
+ }
12088
+ .retold-remote-vlc-setup-note
12089
+ {
12090
+ font-size: 0.7rem;
12091
+ color: var(--retold-text-dim);
12092
+ font-style: italic;
12093
+ margin-top: 4px;
12094
+ }
12095
+ .retold-remote-vlc-setup-toast
12096
+ {
12097
+ position: fixed;
12098
+ bottom: 20px;
12099
+ left: 50%;
12100
+ transform: translateX(-50%);
12101
+ background: var(--retold-bg-secondary);
12102
+ color: var(--retold-accent);
12103
+ padding: 8px 16px;
12104
+ border-radius: 4px;
12105
+ font-size: 0.75rem;
12106
+ z-index: 10000;
12107
+ pointer-events: none;
12108
+ border: 1px solid var(--retold-border);
12109
+ }
12110
+ `};class RetoldRemoteVLCSetupView extends libPictView{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);this._activePlatformTab=this._detectPlatform();this._modalVisible=false;this._boundKeyHandler=null;}_detectPlatform(){let tmpUA=typeof navigator!=='undefined'?navigator.userAgent:'';if(/Macintosh|Mac OS X/.test(tmpUA)){return'macos';}if(/Windows/.test(tmpUA)){return'windows';}return'linux';}openModal(){if(this._modalVisible){return;}this._modalVisible=true;// Create the backdrop
12111
+ let tmpBackdrop=document.createElement('div');tmpBackdrop.className='retold-remote-vlc-modal-backdrop';tmpBackdrop.id='RetoldRemote-VLCSetup-Backdrop';tmpBackdrop.onclick=pEvent=>{if(pEvent.target===tmpBackdrop){this.closeModal();}};// Create the modal
12112
+ let tmpModal=document.createElement('div');tmpModal.className='retold-remote-vlc-modal';// Header
12113
+ let tmpHeader=document.createElement('div');tmpHeader.className='retold-remote-vlc-modal-header';tmpHeader.innerHTML='<span class="retold-remote-vlc-modal-title">VLC Protocol Setup</span>'+'<button class="retold-remote-vlc-modal-close" onclick="pict.views[\'RetoldRemote-VLCSetup\'].closeModal()">X</button>';// Body
12114
+ let tmpBody=document.createElement('div');tmpBody.className='retold-remote-vlc-modal-body';tmpBody.id='RetoldRemote-VLCSetup-Container';tmpModal.appendChild(tmpHeader);tmpModal.appendChild(tmpBody);tmpBackdrop.appendChild(tmpModal);document.body.appendChild(tmpBackdrop);// Render content into the body
12115
+ this._renderVLCSetupContent();// Escape key handler
12116
+ this._boundKeyHandler=pEvent=>{if(pEvent.key==='Escape'){pEvent.preventDefault();pEvent.stopPropagation();this.closeModal();}};document.addEventListener('keydown',this._boundKeyHandler,true);}closeModal(){if(!this._modalVisible){return;}this._modalVisible=false;let tmpBackdrop=document.getElementById('RetoldRemote-VLCSetup-Backdrop');if(tmpBackdrop){tmpBackdrop.remove();}if(this._boundKeyHandler){document.removeEventListener('keydown',this._boundKeyHandler,true);this._boundKeyHandler=null;}}switchPlatformTab(pTab){this._activePlatformTab=pTab;this._renderVLCSetupContent();}_renderVLCSetupContent(){let tmpContainer=document.getElementById('RetoldRemote-VLCSetup-Container');if(!tmpContainer){return;}let tmpPlatform=this._activePlatformTab;let tmpHTML='';// Description
12117
+ tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Stream media directly in VLC from the browser. Press <b>v</b> in the media viewer to launch VLC with the current file.';tmpHTML+='</div>';tmpHTML+='</div>';// Platform status
12118
+ tmpHTML+='<div class="retold-remote-vlc-setup-status">';tmpHTML+='<div class="retold-remote-vlc-setup-status-dot '+(this._detectPlatform()===tmpPlatform?'detected':'unknown')+'"></div>';tmpHTML+='<span>Detected platform: <b>'+this._getPlatformLabel(this._detectPlatform())+'</b></span>';tmpHTML+='</div>';// Platform tabs
12119
+ tmpHTML+='<div class="retold-remote-vlc-setup-platform-tabs">';tmpHTML+=this._buildPlatformTab('macos','macOS',tmpPlatform);tmpHTML+=this._buildPlatformTab('windows','Windows',tmpPlatform);tmpHTML+=this._buildPlatformTab('linux','Linux',tmpPlatform);tmpHTML+='</div>';// Platform-specific content
12120
+ tmpHTML+=this._buildMacOSContent(tmpPlatform);tmpHTML+=this._buildWindowsContent(tmpPlatform);tmpHTML+=this._buildLinuxContent(tmpPlatform);// Test section
12121
+ tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Test</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Click below to test whether the vlc:// protocol handler is registered. VLC should open.';tmpHTML+='</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn" onclick="pict.views[\'RetoldRemote-VLCSetup\'].testProtocol()">Test VLC Protocol</button>';tmpHTML+='</div>';tmpContainer.innerHTML=tmpHTML;}_buildPlatformTab(pKey,pLabel,pActive){let tmpClass='retold-remote-vlc-setup-platform-tab';if(pKey===pActive){tmpClass+=' active';}return'<button class="'+tmpClass+'" onclick="pict.views[\'RetoldRemote-VLCSetup\'].switchPlatformTab(\''+pKey+'\')">'+pLabel+'</button>';}_getPlatformLabel(pKey){if(pKey==='macos')return'macOS';if(pKey==='windows')return'Windows';return'Linux';}_buildMacOSContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='macos'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="macos">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (macOS)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC on macOS does not register a vlc:// protocol handler by default. ';tmpHTML+='An AppleScript app bundle is needed to bridge vlc:// links to VLC. ';tmpHTML+='Run the command below in Terminal to create and register the handler automatically.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Automatic Setup</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Copy and paste this into Terminal:';tmpHTML+='</div>';let tmpScript=this._getMacSetupScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this._escapeHTML(tmpScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyMacSetup()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">What This Does</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates an AppleScript at <code>/tmp/VLCProtocol.applescript</code> that handles vlc:// URLs</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Compiles it into an app bundle at <code>/Applications/VLCProtocol.app</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">3</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Adds the vlc:// URL scheme to the app\'s Info.plist</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">4</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Registers the protocol handler with macOS Launch Services</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">Requires VLC installed at /Applications/VLC.app and Python 3 (included with macOS).</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildWindowsContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='windows'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="windows">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (Windows)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC on Windows registers the vlc:// protocol handler during installation. ';tmpHTML+='If it is not working, you can re-register it by saving and running the registry file below.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option A: Reinstall VLC</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Reinstall VLC and ensure "Register VLC as handler for vlc:// protocol" is checked during installation.</div>';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option B: Registry File</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Save this as <code>vlc-protocol.reg</code> and double-click to import. ';tmpHTML+='Adjust the VLC path if yours differs.';tmpHTML+='</div>';let tmpRegFile=this._getWindowsRegFile();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this._escapeHTML(tmpRegFile)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyWindowsReg()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option C: Batch Script</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Alternatively, save this as <code>vlc-protocol-setup.bat</code> and run as Administrator. ';tmpHTML+='This creates a wrapper script that URL-decodes the vlc:// link before passing it to VLC.';tmpHTML+='</div>';let tmpBatchScript=this._getWindowsBatchScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this._escapeHTML(tmpBatchScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyWindowsBatch()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildLinuxContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='linux'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="linux">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (Linux)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Register a vlc:// protocol handler using a .desktop file and xdg-mime. ';tmpHTML+='Run the command below in a terminal.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup Command</div>';let tmpScript=this._getLinuxSetupScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this._escapeHTML(tmpScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyLinuxSetup()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">What This Does</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates a handler script at <code>~/.local/bin/vlc-protocol</code> that URL-decodes and opens VLC</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates a .desktop file at <code>~/.local/share/applications/vlc-protocol.desktop</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">3</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Registers vlc:// as a URL scheme via <code>xdg-mime</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">Requires VLC and Python 3 installed.</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_getMacSetupScript(){return["# Create the AppleScript handler","cat > /tmp/VLCProtocol.applescript << 'EOF'","on open location theURL","\tset theURL to text 7 thru -1 of theURL","\tset theURL to do shell script \"python3 -c 'import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))' \" & quoted form of theURL","\tdo shell script \"open -a VLC \" & quoted form of theURL","end open location","EOF","","# Compile into app bundle","osacompile -o /Applications/VLCProtocol.app /tmp/VLCProtocol.applescript","","# Add vlc:// URL scheme to Info.plist","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes array\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0 dict\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLName string 'VLC Protocol'\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes array\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string vlc\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","","# Register with Launch Services","/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \\"," -f /Applications/VLCProtocol.app","","echo \"VLC protocol handler installed successfully.\""].join('\n');}_getWindowsRegFile(){return["Windows Registry Editor Version 5.00","","[HKEY_CLASSES_ROOT\\vlc]","@=\"URL:VLC Protocol\"","\"URL Protocol\"=\"\"","","[HKEY_CLASSES_ROOT\\vlc\\shell]","","[HKEY_CLASSES_ROOT\\vlc\\shell\\open]","","[HKEY_CLASSES_ROOT\\vlc\\shell\\open\\command]","@=\"\\\"C:\\\\Program Files\\\\VideoLAN\\\\VLC\\\\vlc.exe\\\" \\\"%1\\\"\""].join('\n');}_getWindowsBatchScript(){return["@echo off","REM VLC Protocol Handler Setup for Windows","REM Run this as Administrator","","REM Create the handler script","mkdir \"%APPDATA%\\VLCProtocol\" 2>nul","(","echo import sys, urllib.parse, subprocess","echo url = sys.argv[1] if len(sys.argv^) ^> 1 else ''","echo if url.startswith('vlc://'^): url = url[6:]","echo url = urllib.parse.unquote(url^)","echo subprocess.Popen(['C:\\\\Program Files\\\\VideoLAN\\\\VLC\\\\vlc.exe', url]^)",") > \"%APPDATA%\\VLCProtocol\\handler.py\"","","REM Register the protocol in the registry","reg add \"HKCU\\Software\\Classes\\vlc\" /ve /d \"URL:VLC Protocol\" /f","reg add \"HKCU\\Software\\Classes\\vlc\" /v \"URL Protocol\" /d \"\" /f","reg add \"HKCU\\Software\\Classes\\vlc\\shell\\open\\command\" /ve /d \"pythonw \\\"%APPDATA%\\VLCProtocol\\handler.py\\\" \\\"%%1\\\"\" /f","","echo VLC protocol handler installed successfully.","pause"].join('\n');}_getLinuxSetupScript(){return["# Create handler script","mkdir -p ~/.local/bin","cat > ~/.local/bin/vlc-protocol << 'EOF'","#!/bin/bash","URL=\"$1\"","URL=\"${URL#vlc://}\"","URL=$(python3 -c \"import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))\" \"$URL\")","exec vlc \"$URL\" &","EOF","chmod +x ~/.local/bin/vlc-protocol","","# Create .desktop file","cat > ~/.local/share/applications/vlc-protocol.desktop << 'EOF'","[Desktop Entry]","Name=VLC Protocol Handler","Exec=bash -c '~/.local/bin/vlc-protocol %u'","Type=Application","NoDisplay=true","MimeType=x-scheme-handler/vlc;","EOF","","# Register the handler","xdg-mime default vlc-protocol.desktop x-scheme-handler/vlc","update-desktop-database ~/.local/share/applications/","","echo \"VLC protocol handler installed successfully.\""].join('\n');}_escapeHTML(pStr){return pStr.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}_copyToClipboard(pText,pLabel){if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(pText).then(()=>{this._showToast(pLabel+' copied to clipboard');}).catch(()=>{this._fallbackCopy(pText,pLabel);});}else{this._fallbackCopy(pText,pLabel);}}_fallbackCopy(pText,pLabel){let tmpTextarea=document.createElement('textarea');tmpTextarea.value=pText;tmpTextarea.style.position='fixed';tmpTextarea.style.left='-9999px';document.body.appendChild(tmpTextarea);tmpTextarea.select();try{document.execCommand('copy');this._showToast(pLabel+' copied to clipboard');}catch(pErr){this._showToast('Failed to copy - please select and copy manually');}document.body.removeChild(tmpTextarea);}_showToast(pMessage){let tmpExisting=document.querySelector('.retold-remote-vlc-setup-toast');if(tmpExisting){tmpExisting.remove();}let tmpToast=document.createElement('div');tmpToast.className='retold-remote-vlc-setup-toast';tmpToast.textContent=pMessage;document.body.appendChild(tmpToast);setTimeout(()=>{if(tmpToast.parentNode){tmpToast.remove();}},2000);}copyMacSetup(){this._copyToClipboard(this._getMacSetupScript(),'macOS setup script');}copyWindowsReg(){this._copyToClipboard(this._getWindowsRegFile(),'Registry file');}copyWindowsBatch(){this._copyToClipboard(this._getWindowsBatchScript(),'Batch script');}copyLinuxSetup(){this._copyToClipboard(this._getLinuxSetupScript(),'Linux setup script');}testProtocol(){let tmpIsWindows=/Windows/.test(navigator.userAgent);let tmpSampleURL='https://www.sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4';let tmpTestURL=tmpIsWindows?'vlc://'+tmpSampleURL:'vlc://'+encodeURIComponent(tmpSampleURL);let tmpLink=document.createElement('a');tmpLink.href=tmpTestURL;tmpLink.style.display='none';document.body.appendChild(tmpLink);tmpLink.click();document.body.removeChild(tmpLink);}}RetoldRemoteVLCSetupView.default_configuration=_ViewConfiguration;module.exports=RetoldRemoteVLCSetupView;},{"pict-view":76}],130:[function(require,module,exports){const libPictView=require('pict-view');const _ViewConfiguration={ViewIdentifier:"RetoldRemote-VideoExplorer",DefaultRenderable:"RetoldRemote-VideoExplorer",DefaultDestinationAddress:"#RetoldRemote-Viewer-Container",AutoRender:false,CSS:/*css*/`
11183
12122
  .retold-remote-vex
11184
12123
  {
11185
12124
  display: flex;