2026-06-03 14:28:50 +03:00
( function ( ) {
var preview = document . querySelector ( "[data-source-url][data-file-name][data-content-type]" ) ;
if ( ! preview ) {
return ;
}
var SMALL _TEXT _BYTES = 50 * 1024 ;
var LARGE _PREVIEW _BYTES = 500 * 1024 ;
var state = {
fileName : preview . dataset . fileName || "" ,
contentType : ( preview . dataset . contentType || "" ) . toLowerCase ( ) ,
previewKind : preview . dataset . previewKind || "" ,
sizeBytes : Number ( preview . dataset . sizeBytes || 0 ) ,
sizeLabel : preview . dataset . fileSize || "" ,
sourceURL : preview . dataset . sourceUrl || "" ,
downloadURL : preview . dataset . downloadUrl || "" ,
iconURL : preview . dataset . iconUrl || "" ,
2026-06-05 10:42:30 +03:00
sceneURL : preview . dataset . sceneUrl || "" ,
2026-06-08 03:43:43 +03:00
archiveURL : preview . dataset . archiveUrl || "" ,
2026-06-03 14:28:50 +03:00
activeMode : "" ,
defaultMode : "default" ,
pendingMode : "" ,
textSource : "" ,
textLoaded : false ,
rawLoaded : false ,
prismLoaded : false ,
renderLoaded : false ,
2026-06-05 10:42:30 +03:00
sceneLoaded : false ,
2026-06-08 03:43:43 +03:00
archiveLoaded : false ,
archiveUIRendered : false ,
archiveData : null ,
archiveText : "" ,
2026-06-03 14:28:50 +03:00
renderFullscreenFallback : false ,
confirmedLargeModes : { } ,
tabs : [ ]
} ;
var els = {
tabs : preview . querySelector ( "[data-preview-tabs]" ) ,
modeLabel : preview . querySelector ( "[data-preview-mode-label]" ) ,
defaultPane : preview . querySelector ( "[data-default-preview]" ) ,
imagePane : preview . querySelector ( "[data-image-preview]" ) ,
videoPane : preview . querySelector ( "[data-video-preview]" ) ,
2026-06-05 10:42:30 +03:00
videoScenesPane : preview . querySelector ( "[data-video-scenes-preview]" ) ,
2026-06-03 14:28:50 +03:00
browserAudioPane : preview . querySelector ( "[data-browser-audio-preview]" ) ,
rawPane : preview . querySelector ( "[data-raw-preview]" ) ,
rawOutput : preview . querySelector ( "[data-raw-output]" ) ,
codePane : preview . querySelector ( "[data-code-preview]" ) ,
codeOutput : preview . querySelector ( "[data-code-output]" ) ,
2026-06-08 03:43:43 +03:00
archiveBrowserPane : preview . querySelector ( "[data-archive-browser-preview]" ) ,
archivePane : preview . querySelector ( "[data-archive-preview]" ) ,
archiveOutput : preview . querySelector ( "[data-archive-output]" ) ,
2026-06-03 14:28:50 +03:00
renderPane : preview . querySelector ( "[data-render-preview]" ) ,
fullscreenButton : preview . querySelector ( "[data-render-fullscreen]" ) ,
gatePane : preview . querySelector ( "[data-large-preview-gate]" ) ,
gateConfirm : preview . querySelector ( "[data-large-preview-confirm]" ) ,
gateCancel : preview . querySelector ( "[data-large-preview-cancel]" ) ,
placeholder : preview . querySelector ( "[data-preview-placeholder]" )
} ;
var fileType = detectFileType ( ) ;
state . tabs = buildTabs ( fileType ) ;
state . defaultMode = chooseDefaultMode ( fileType , state . tabs ) ;
bindLargeGate ( ) ;
bindThemeChanges ( ) ;
bindRenderFullscreen ( ) ;
2026-06-10 18:14:29 +03:00
configureMediaSession ( ) ;
2026-06-03 14:28:50 +03:00
renderTabs ( ) ;
selectMode ( state . defaultMode ) ;
function detectFileType ( ) {
var extension = extensionFor ( state . fileName ) ;
var baseType = state . contentType . split ( ";" ) [ 0 ] . trim ( ) ;
var language = languageFor ( extension , baseType ) ;
var isImage = state . previewKind === "image" || baseType . indexOf ( "image/" ) === 0 && baseType !== "image/svg+xml" ;
var isVideo = state . previewKind === "video" || baseType . indexOf ( "video/" ) === 0 ;
var isAudio = state . previewKind === "audio" || baseType . indexOf ( "audio/" ) === 0 ;
2026-06-08 03:43:43 +03:00
var isArchive = Boolean ( state . archiveURL ) && isArchiveFile ( extension , baseType ) ;
2026-06-03 14:28:50 +03:00
return {
extension : extension ,
baseType : baseType ,
language : language ,
isTextLike : Boolean ( language ) ,
isHTML : language === "html" ,
isMarkdown : language === "markdown" ,
isImage : isImage ,
isVideo : isVideo ,
isAudio : isAudio ,
2026-06-08 03:43:43 +03:00
isArchive : isArchive ,
2026-06-03 14:28:50 +03:00
isMobile : window . matchMedia && window . matchMedia ( "(max-width: 720px), (pointer: coarse)" ) . matches
} ;
}
function buildTabs ( type ) {
var tabs = [ { mode : "default" , label : "Default" } ] ;
if ( type . isImage ) {
tabs . push ( { mode : "image" , label : "Image Preview" } ) ;
return tabs ;
}
if ( type . isVideo ) {
tabs . push ( { mode : "video" , label : "Video Preview" } ) ;
2026-06-05 10:42:30 +03:00
if ( state . sceneURL && els . videoScenesPane ) {
tabs . push ( { mode : "scenes" , label : "Scenes Preview" } ) ;
}
2026-06-03 14:28:50 +03:00
return tabs ;
}
if ( type . isAudio ) {
tabs . push ( { mode : "browser-audio" , label : "Browser Preview" } ) ;
return tabs ;
}
2026-06-08 03:43:43 +03:00
if ( type . isArchive ) {
tabs . push ( { mode : "archive-ui" , label : "Archive Preview" } ) ;
tabs . push ( { mode : "archive" , label : "Text Tree" } ) ;
return tabs ;
}
2026-06-03 14:28:50 +03:00
if ( type . isTextLike ) {
if ( type . isHTML || type . isMarkdown ) {
tabs . push ( { mode : "render" , label : "Render Preview" } ) ;
}
tabs . push ( { mode : "raw" , label : "Raw Preview" } ) ;
tabs . push ( { mode : "code" , label : "Code Preview" } ) ;
}
return tabs ;
}
function chooseDefaultMode ( type , tabs ) {
if ( type . isImage ) {
return "image" ;
}
if ( type . isVideo ) {
return "video" ;
}
2026-06-08 03:43:43 +03:00
if ( type . isArchive ) {
return "archive-ui" ;
}
2026-06-03 14:55:19 +03:00
if ( state . sizeBytes > LARGE _PREVIEW _BYTES ) {
if ( type . isAudio && hasMode ( tabs , "browser-audio" ) ) {
return "browser-audio" ;
}
return "default" ;
}
2026-06-03 14:28:50 +03:00
if ( type . isAudio ) {
return "browser-audio" ;
}
if ( type . isTextLike && state . sizeBytes > SMALL _TEXT _BYTES ) {
return "raw" ;
}
if ( type . isMarkdown ) {
return "render" ;
}
if ( type . isTextLike ) {
return "code" ;
}
return "default" ;
}
function renderTabs ( ) {
if ( ! els . tabs ) {
return ;
}
els . tabs . innerHTML = "" ;
state . tabs . forEach ( function ( tab ) {
var button = document . createElement ( "button" ) ;
button . className = "preview-tab" ;
button . type = "button" ;
button . dataset . previewTab = tab . mode ;
button . textContent = tab . label ;
button . addEventListener ( "click" , function ( ) {
selectMode ( tab . mode ) ;
} ) ;
els . tabs . appendChild ( button ) ;
} ) ;
}
function selectMode ( mode ) {
if ( ! hasMode ( state . tabs , mode ) ) {
mode = "default" ;
}
if ( mode !== "render" ) {
exitRenderFullscreen ( ) ;
}
if ( requiresLargeConfirmation ( mode ) ) {
showLargeGate ( mode ) ;
return ;
}
state . activeMode = mode ;
updateTabs ( mode ) ;
updateRenderFullscreenButton ( ) ;
hideAllPanes ( ) ;
setModeLabel ( labelForMode ( mode ) ) ;
if ( mode === "default" ) {
show ( els . defaultPane ) ;
} else if ( mode === "image" ) {
show ( els . imagePane ) ;
} else if ( mode === "video" ) {
show ( els . videoPane ) ;
2026-06-05 10:42:30 +03:00
} else if ( mode === "scenes" ) {
show ( els . videoScenesPane ) ;
ensureScenesPreview ( ) ;
2026-06-03 14:28:50 +03:00
} else if ( mode === "browser-audio" ) {
show ( els . browserAudioPane ) ;
} else if ( mode === "raw" ) {
show ( els . rawPane ) ;
ensureRawPreview ( ) ;
} else if ( mode === "code" ) {
show ( els . codePane ) ;
ensurePrismPreview ( ) ;
2026-06-08 03:43:43 +03:00
} else if ( mode === "archive-ui" ) {
show ( els . archiveBrowserPane ) ;
ensureArchiveBrowserPreview ( ) ;
} else if ( mode === "archive" ) {
show ( els . archivePane ) ;
ensureArchivePreview ( ) ;
2026-06-03 14:28:50 +03:00
} else if ( mode === "render" ) {
show ( els . renderPane ) ;
if ( fileType . isMarkdown ) {
ensureMarkdownRenderPreview ( ) ;
} else {
ensureHTMLRenderPreview ( ) ;
}
}
}
function requiresLargeConfirmation ( mode ) {
if ( state . sizeBytes <= LARGE _PREVIEW _BYTES || state . confirmedLargeModes [ mode ] ) {
return false ;
}
return mode === "raw" || mode === "code" || mode === "render" ;
}
function showLargeGate ( mode ) {
state . pendingMode = mode ;
updateTabs ( state . activeMode || state . defaultMode ) ;
updateRenderFullscreenButton ( false ) ;
hideAllPanes ( ) ;
show ( els . gatePane ) ;
setModeLabel ( "Large preview" ) ;
}
function bindLargeGate ( ) {
if ( els . gateConfirm ) {
els . gateConfirm . addEventListener ( "click" , function ( ) {
if ( ! state . pendingMode ) {
return ;
}
state . confirmedLargeModes [ state . pendingMode ] = true ;
selectMode ( state . pendingMode ) ;
} ) ;
}
if ( els . gateCancel ) {
els . gateCancel . addEventListener ( "click" , function ( ) {
state . pendingMode = "" ;
selectMode ( state . activeMode || state . defaultMode || "default" ) ;
} ) ;
}
}
function bindThemeChanges ( ) {
var themeSelect = document . querySelector ( "[data-theme-select]" ) ;
if ( ! themeSelect ) {
return ;
}
themeSelect . addEventListener ( "change" , function ( ) {
window . setTimeout ( function ( ) {
if ( ! fileType . isMarkdown || ! state . renderLoaded ) {
return ;
}
state . renderLoaded = false ;
if ( state . activeMode === "render" ) {
ensureMarkdownRenderPreview ( ) ;
}
} , 0 ) ;
} ) ;
}
function bindRenderFullscreen ( ) {
if ( ! els . fullscreenButton ) {
return ;
}
els . fullscreenButton . addEventListener ( "click" , function ( ) {
if ( isRenderFullscreen ( ) ) {
exitRenderFullscreen ( ) ;
return ;
}
enterRenderFullscreen ( ) ;
} ) ;
document . addEventListener ( "fullscreenchange" , updateRenderFullscreenButton ) ;
}
2026-06-10 18:14:29 +03:00
function configureMediaSession ( ) {
if ( ! ( "mediaSession" in navigator ) || typeof window . MediaMetadata !== "function" ) {
return ;
}
if ( ! fileType . isAudio && ! fileType . isVideo ) {
return ;
}
var artworkURL = "" ;
if ( fileType . isVideo && els . videoPane ) {
artworkURL = els . videoPane . getAttribute ( "poster" ) || state . iconURL || "" ;
} else {
artworkURL = state . iconURL || "" ;
}
var metadata = {
title : state . fileName || "Warpbox media" ,
artist : "Warpbox" ,
album : state . sizeLabel || state . contentType || ""
} ;
if ( artworkURL ) {
metadata . artwork = [
{ src : window . Warpbox . absoluteURL ( artworkURL ) , sizes : "512x512" , type : "image/png" }
] ;
}
navigator . mediaSession . metadata = new MediaMetadata ( metadata ) ;
}
2026-06-03 14:28:50 +03:00
function ensureTextLoaded ( ) {
if ( state . textLoaded ) {
return Promise . resolve ( state . textSource ) ;
}
showLoading ( "Loading preview..." ) ;
return fetch ( state . sourceURL , { credentials : "same-origin" } )
. then ( function ( response ) {
if ( ! response . ok ) {
throw new Error ( "Preview failed" ) ;
}
return response . text ( ) ;
} )
. then ( function ( source ) {
state . textSource = source ;
state . textLoaded = true ;
hide ( els . placeholder ) ;
return source ;
} )
. catch ( function ( error ) {
showError ( "Preview unavailable" ) ;
throw error ;
} ) ;
}
function ensureRawPreview ( ) {
if ( state . rawLoaded ) {
return ;
}
ensureTextLoaded ( ) . then ( function ( source ) {
els . rawOutput . textContent = source ;
state . rawLoaded = true ;
if ( state . activeMode === "raw" ) {
hide ( els . placeholder ) ;
show ( els . rawPane ) ;
}
} ) ;
}
function ensurePrismPreview ( ) {
if ( state . prismLoaded ) {
return ;
}
showLoading ( "Loading syntax preview..." ) ;
Promise . all ( [ ensureTextLoaded ( ) , loadPrism ( ) ] )
. then ( function ( results ) {
var source = results [ 0 ] ;
var language = fileType . language ;
if ( language === "json" ) {
source = formatJSON ( source ) ;
}
els . codeOutput . className = "language-" + language ;
els . codeOutput . textContent = source ;
if ( window . Prism ) {
window . Prism . highlightElement ( els . codeOutput ) ;
}
state . prismLoaded = true ;
if ( state . activeMode === "code" ) {
hide ( els . placeholder ) ;
show ( els . codePane ) ;
}
} )
. catch ( function ( ) {
showError ( "Syntax preview unavailable" ) ;
} ) ;
}
function ensureHTMLRenderPreview ( ) {
if ( state . renderLoaded ) {
return ;
}
showLoading ( "Rendering preview..." ) ;
ensureTextLoaded ( )
. then ( function ( source ) {
els . renderPane . srcdoc = withBaseElement ( source ) ;
state . renderLoaded = true ;
if ( state . activeMode === "render" ) {
hide ( els . placeholder ) ;
show ( els . renderPane ) ;
}
} )
. catch ( function ( ) {
showError ( "Render preview unavailable" ) ;
} ) ;
}
function ensureMarkdownRenderPreview ( ) {
if ( state . renderLoaded ) {
return ;
}
showLoading ( "Rendering Markdown..." ) ;
Promise . all ( [ ensureTextLoaded ( ) , loadMarkdownLibs ( ) ] )
. then ( function ( results ) {
var markdown = results [ 0 ] ;
var html = parseMarkdown ( markdown ) ;
var clean = window . DOMPurify . sanitize ( html ) ;
els . renderPane . srcdoc = markdownDocument ( clean ) ;
state . renderLoaded = true ;
if ( state . activeMode === "render" ) {
hide ( els . placeholder ) ;
show ( els . renderPane ) ;
}
} )
. catch ( function ( ) {
showError ( "Markdown preview unavailable" ) ;
} ) ;
}
function showLoading ( message ) {
if ( ! els . placeholder ) {
return ;
}
var text = els . placeholder . querySelector ( "p" ) ;
if ( text ) {
text . textContent = message ;
}
show ( els . placeholder ) ;
}
function showError ( message ) {
hideAllPanes ( ) ;
var text = els . placeholder && els . placeholder . querySelector ( "p" ) ;
if ( text ) {
text . textContent = message ;
}
show ( els . placeholder ) ;
}
function hideAllPanes ( ) {
hide ( els . defaultPane ) ;
hide ( els . imagePane ) ;
hide ( els . videoPane ) ;
2026-06-05 10:42:30 +03:00
hide ( els . videoScenesPane ) ;
2026-06-03 14:28:50 +03:00
hide ( els . browserAudioPane ) ;
hide ( els . rawPane ) ;
hide ( els . codePane ) ;
2026-06-08 03:43:43 +03:00
hide ( els . archiveBrowserPane ) ;
hide ( els . archivePane ) ;
2026-06-03 14:28:50 +03:00
hide ( els . renderPane ) ;
hide ( els . gatePane ) ;
hide ( els . placeholder ) ;
}
function enterRenderFullscreen ( ) {
if ( state . activeMode !== "render" ) {
return ;
}
if ( preview . requestFullscreen ) {
var request = preview . requestFullscreen ( ) ;
if ( request && typeof request . catch === "function" ) {
request . catch ( function ( ) {
state . renderFullscreenFallback = true ;
preview . classList . add ( "is-render-fullscreen" ) ;
updateRenderFullscreenButton ( ) ;
} ) ;
}
return ;
}
state . renderFullscreenFallback = true ;
preview . classList . add ( "is-render-fullscreen" ) ;
updateRenderFullscreenButton ( ) ;
}
function exitRenderFullscreen ( ) {
if ( document . fullscreenElement === preview && document . exitFullscreen ) {
var exit = document . exitFullscreen ( ) ;
if ( exit && typeof exit . catch === "function" ) {
exit . catch ( function ( ) { } ) ;
}
}
state . renderFullscreenFallback = false ;
preview . classList . remove ( "is-render-fullscreen" ) ;
updateRenderFullscreenButton ( ) ;
}
function isRenderFullscreen ( ) {
return document . fullscreenElement === preview || state . renderFullscreenFallback ;
}
function updateRenderFullscreenButton ( forceVisible ) {
if ( ! els . fullscreenButton ) {
return ;
}
var visible = typeof forceVisible === "boolean" ? forceVisible : state . activeMode === "render" ;
els . fullscreenButton . hidden = ! visible ;
els . fullscreenButton . textContent = isRenderFullscreen ( ) ? "Exit Full Screen" : "Full Screen" ;
els . fullscreenButton . setAttribute ( "aria-pressed" , isRenderFullscreen ( ) ? "true" : "false" ) ;
}
function updateTabs ( mode ) {
if ( ! els . tabs ) {
return ;
}
Array . prototype . forEach . call ( els . tabs . querySelectorAll ( "[data-preview-tab]" ) , function ( button ) {
button . classList . toggle ( "is-active" , button . dataset . previewTab === mode ) ;
} ) ;
}
function show ( element ) {
if ( element ) {
element . hidden = false ;
}
}
function hide ( element ) {
if ( element ) {
element . hidden = true ;
}
}
function setModeLabel ( label ) {
if ( els . modeLabel ) {
els . modeLabel . textContent = label ;
}
}
function hasMode ( tabs , mode ) {
return tabs . some ( function ( tab ) {
return tab . mode === mode ;
} ) ;
}
function labelForMode ( mode ) {
var labels = {
"default" : "Default" ,
"image" : "Image preview" ,
"video" : "Video preview" ,
2026-06-05 10:42:30 +03:00
"scenes" : "Scenes preview" ,
2026-06-03 14:28:50 +03:00
"browser-audio" : "Browser preview" ,
"raw" : "Raw preview" ,
"code" : "Code preview" ,
2026-06-08 03:43:43 +03:00
"archive-ui" : "Archive preview" ,
"archive" : "Archive preview" ,
2026-06-03 14:28:50 +03:00
"render" : "Render preview"
} ;
return labels [ mode ] || "Preview" ;
}
2026-06-05 10:42:30 +03:00
function ensureScenesPreview ( ) {
if ( state . sceneLoaded || ! els . videoScenesPane ) {
return ;
}
var src = els . videoScenesPane . dataset . sceneSrc || state . sceneURL ;
if ( ! src ) {
return ;
}
els . videoScenesPane . src = src ;
state . sceneLoaded = true ;
}
2026-06-08 03:43:43 +03:00
function ensureArchivePreview ( ) {
if ( state . archiveLoaded || ! els . archiveOutput || ! state . archiveURL ) {
return ;
}
ensureArchiveData ( )
. then ( function ( ) {
var text = state . archiveText || archiveDataToText ( state . archiveData ) ;
els . archiveOutput . textContent = text ;
state . archiveLoaded = true ;
hide ( els . placeholder ) ;
show ( els . archivePane ) ;
} )
. catch ( function ( ) {
showError ( "Archive preview could not be loaded." ) ;
} ) ;
}
function ensureArchiveBrowserPreview ( ) {
if ( state . archiveUIRendered || ! els . archiveBrowserPane || ! state . archiveURL ) {
return ;
}
ensureArchiveData ( )
. then ( function ( ) {
renderArchiveBrowser ( state . archiveData ) ;
state . archiveUIRendered = true ;
hide ( els . placeholder ) ;
show ( els . archiveBrowserPane ) ;
} )
. catch ( function ( ) {
showError ( "Archive preview could not be loaded." ) ;
} ) ;
}
function ensureArchiveData ( ) {
if ( state . archiveData || state . archiveText ) {
return Promise . resolve ( ) ;
}
showLoading ( "Loading archive contents..." ) ;
return fetch ( state . archiveURL , { credentials : "same-origin" } )
. then ( function ( response ) {
if ( ! response . ok ) {
throw new Error ( "Archive preview could not be loaded." ) ;
}
return response . text ( ) ;
} )
. then ( function ( text ) {
try {
state . archiveData = JSON . parse ( text ) ;
} catch ( error ) {
state . archiveText = text ;
}
} ) ;
}
function renderArchiveBrowser ( data ) {
if ( ! els . archiveBrowserPane ) {
return ;
}
els . archiveBrowserPane . innerHTML = "" ;
if ( ! data || ! data . root ) {
var fallback = document . createElement ( "pre" ) ;
fallback . className = "archive-browser-legacy" ;
fallback . textContent = state . archiveText || "Archive preview is unavailable." ;
els . archiveBrowserPane . appendChild ( fallback ) ;
return ;
}
var header = document . createElement ( "div" ) ;
header . className = "archive-browser-header" ;
header . innerHTML = "<strong></strong><span></span>" ;
header . querySelector ( "strong" ) . textContent = data . name || state . fileName || "Archive" ;
header . querySelector ( "span" ) . textContent = [
data . type || "Archive" ,
formatArchiveCount ( data . fileCount , "file" ) ,
formatArchiveCount ( data . folderCount , "folder" ) ,
formatBytes ( data . uncompressedSize || 0 )
] . filter ( Boolean ) . join ( " · " ) ;
els . archiveBrowserPane . appendChild ( header ) ;
var tree = document . createElement ( "div" ) ;
tree . className = "archive-tree" ;
var items = data . root . items || [ ] ;
if ( items . length === 0 ) {
var emptyTree = document . createElement ( "p" ) ;
emptyTree . className = "archive-browser-empty" ;
emptyTree . textContent = "This archive is empty." ;
tree . appendChild ( emptyTree ) ;
} else {
items . forEach ( function ( item ) {
tree . appendChild ( renderArchiveNode ( item , 0 ) ) ;
} ) ;
}
els . archiveBrowserPane . appendChild ( tree ) ;
}
function renderArchiveNode ( node , depth ) {
var row = document . createElement ( node . dir ? "details" : "div" ) ;
row . className = node . dir ? "archive-node archive-folder" : "archive-node archive-file" ;
if ( node . dir && depth < 1 ) {
row . open = true ;
}
var summary = document . createElement ( node . dir ? "summary" : "div" ) ;
summary . className = "archive-node-row" ;
summary . style . paddingLeft = ( 0.45 + depth * 1.15 ) . toFixed ( 2 ) + "rem" ;
if ( node . dir ) {
summary . appendChild ( createArchiveChevron ( ) ) ;
} else {
var spacer = document . createElement ( "span" ) ;
spacer . className = "archive-chevron-spacer" ;
summary . appendChild ( spacer ) ;
}
summary . appendChild ( createArchiveIcon ( node . icon || ( node . dir ? "folder" : "file" ) ) ) ;
var name = document . createElement ( "span" ) ;
name . className = "archive-node-name" ;
name . textContent = node . name + ( node . dir ? "/" : "" ) ;
summary . appendChild ( name ) ;
if ( ! node . dir ) {
var size = document . createElement ( "span" ) ;
size . className = "archive-node-size" ;
size . textContent = formatBytes ( node . size || 0 ) ;
summary . appendChild ( size ) ;
}
row . appendChild ( summary ) ;
if ( node . dir ) {
( node . items || [ ] ) . forEach ( function ( child ) {
row . appendChild ( renderArchiveNode ( child , depth + 1 ) ) ;
} ) ;
}
return row ;
}
function createArchiveChevron ( ) {
var chevron = document . createElement ( "span" ) ;
chevron . className = "archive-chevron" ;
chevron . setAttribute ( "aria-hidden" , "true" ) ;
chevron . innerHTML = '<svg viewBox="0 0 24 24" focusable="false"><path d="m9 6 6 6-6 6"/></svg>' ;
return chevron ;
}
function createArchiveIcon ( icon ) {
var element = document . createElement ( "span" ) ;
element . className = "archive-file-icon archive-file-icon-" + icon ;
element . setAttribute ( "aria-hidden" , "true" ) ;
2026-06-08 03:50:14 +03:00
element . innerHTML = '<span class="archive-svg-icon">' + archiveIconSVG ( icon ) + '</span>' ;
var retroURL = archiveRetroIconURL ( icon ) ;
if ( retroURL ) {
var retro = document . createElement ( "img" ) ;
retro . className = "archive-retro-icon" ;
retro . src = retroURL ;
retro . alt = "" ;
retro . decoding = "async" ;
retro . loading = "lazy" ;
element . appendChild ( retro ) ;
}
2026-06-08 03:43:43 +03:00
return element ;
}
function archiveDataToText ( data ) {
if ( ! data || ! data . root ) {
return state . archiveText || "" ;
}
var lines = [
"Archive preview" ,
"Name: " + ( data . name || state . fileName || "Archive" ) ,
"Type: " + ( data . type || "Archive" ) ,
"Entries: " + ( data . fileCount || 0 ) + " files, " + ( data . folderCount || 0 ) + " folders" ,
"Uncompressed size: " + formatBytes ( data . uncompressedSize || 0 ) ,
"" ,
"."
] ;
appendArchiveTextLines ( lines , data . root . items || [ ] , "" ) ;
if ( ! ( data . root . items || [ ] ) . length ) {
lines . push ( "(empty archive)" ) ;
}
return lines . join ( "\n" ) ;
}
function appendArchiveTextLines ( lines , items , prefix ) {
items . forEach ( function ( item , index ) {
var last = index === items . length - 1 ;
var branch = last ? "`-- " : "|-- " ;
var nextPrefix = prefix + ( last ? " " : "| " ) ;
var label = item . dir ? "[DIR] " + item . name + "/" : "[" + ( item . icon || "file" ) . toUpperCase ( ) + "] " + item . name + " (" + formatBytes ( item . size || 0 ) + ")" ;
lines . push ( prefix + branch + label ) ;
if ( item . dir ) {
appendArchiveTextLines ( lines , item . items || [ ] , nextPrefix ) ;
}
} ) ;
}
function archiveIconSVG ( icon ) {
var icons = {
folder : '<svg viewBox="0 0 24 24" focusable="false"><path d="M3 6.75A2.75 2.75 0 0 1 5.75 4h4.1l2 2.2h6.4A2.75 2.75 0 0 1 21 8.95v8.3A2.75 2.75 0 0 1 18.25 20H5.75A2.75 2.75 0 0 1 3 17.25Z"/></svg>' ,
img : '<svg viewBox="0 0 24 24" focusable="false"><rect x="4" y="5" width="16" height="14" rx="2"/><path d="m7 16 3.2-3.2 2.6 2.6 2.2-2.2L19 17"/><circle cx="9" cy="9" r="1.4"/></svg>' ,
vid : '<svg viewBox="0 0 24 24" focusable="false"><rect x="4" y="6" width="12" height="12" rx="2"/><path d="m16 10 4-2.5v9L16 14"/></svg>' ,
aud : '<svg viewBox="0 0 24 24" focusable="false"><path d="M9 18V6l10-2v12"/><circle cx="7" cy="18" r="3"/><circle cx="17" cy="16" r="3"/></svg>' ,
txt : '<svg viewBox="0 0 24 24" focusable="false"><path d="M7 3h7l4 4v14H7Z"/><path d="M14 3v5h5M9 12h6M9 15h6M9 18h4"/></svg>' ,
code : '<svg viewBox="0 0 24 24" focusable="false"><path d="m9 8-4 4 4 4M15 8l4 4-4 4M13 5l-2 14"/></svg>' ,
arc : '<svg viewBox="0 0 24 24" focusable="false"><path d="M7 3h7l4 4v14H7Z"/><path d="M14 3v5h5M10 6h2M10 9h2M10 12h2M10 15h2M10 18h2"/></svg>' ,
file : '<svg viewBox="0 0 24 24" focusable="false"><path d="M7 3h7l4 4v14H7Z"/><path d="M14 3v5h5"/></svg>'
} ;
return icons [ icon ] || icons . file ;
}
2026-06-08 03:50:14 +03:00
function archiveRetroIconURL ( icon ) {
var base = "/static/file-icons/retro/" ;
var icons = {
folder : "directory_open_file_mydocs-4.png" ,
img : "shimgvw.dll_14_1-2.png" ,
vid : "wmploc.dll_14_504-2.png" ,
aud : "wmploc.dll_14_610-2.png" ,
txt : "shell32.dll_14_151-2.png" ,
code : "mshtml.dll_14_2660-2.png" ,
arc : "zipfldr.dll_14_101-2.png" ,
file : "shell32.dll_14_152-2.png"
} ;
return base + ( icons [ icon ] || icons . file ) ;
}
2026-06-08 03:43:43 +03:00
function formatArchiveCount ( value , label ) {
value = Number ( value || 0 ) ;
return value + " " + label + ( value === 1 ? "" : "s" ) ;
}
function formatBytes ( value ) {
value = Number ( value || 0 ) ;
if ( value < 1024 ) {
return value + " B" ;
}
var units = [ "KiB" , "MiB" , "GiB" , "TiB" ] ;
var size = value / 1024 ;
for ( var i = 0 ; i < units . length ; i ++ ) {
if ( size < 1024 || i === units . length - 1 ) {
return size . toFixed ( 1 ) + " " + units [ i ] ;
}
size /= 1024 ;
}
return value + " B" ;
}
2026-06-03 14:28:50 +03:00
function loadPrism ( ) {
if ( window . Prism ) {
return Promise . resolve ( ) ;
}
window . Prism = window . Prism || { } ;
window . Prism . manual = true ;
return Promise . all ( [
loadStyle ( "/static/lib/prismjs/prism.css" ) ,
loadScript ( "/static/lib/prismjs/prism.js" )
] ) ;
}
function loadMarkdownLibs ( ) {
if ( window . marked && window . DOMPurify ) {
return Promise . resolve ( ) ;
}
return Promise . all ( [
loadScript ( "/static/lib/markdown/marked.umd.js" ) ,
loadScript ( "/static/lib/markdown/purify.min.js" )
] ) ;
}
function loadScript ( src ) {
return new Promise ( function ( resolve , reject ) {
var existing = document . querySelector ( 'script[src="' + src + '"]' ) ;
if ( existing ) {
if ( existing . dataset . loaded === "true" ) {
resolve ( ) ;
return ;
}
existing . addEventListener ( "load" , resolve , { once : true } ) ;
existing . addEventListener ( "error" , reject , { once : true } ) ;
return ;
}
var script = document . createElement ( "script" ) ;
script . async = true ;
script . src = src ;
script . addEventListener ( "load" , function ( ) {
script . dataset . loaded = "true" ;
resolve ( ) ;
} , { once : true } ) ;
script . addEventListener ( "error" , reject , { once : true } ) ;
document . head . appendChild ( script ) ;
} ) ;
}
function loadStyle ( href ) {
if ( document . querySelector ( 'link[href="' + href + '"]' ) ) {
return Promise . resolve ( ) ;
}
return new Promise ( function ( resolve , reject ) {
var link = document . createElement ( "link" ) ;
link . rel = "stylesheet" ;
link . href = href ;
link . addEventListener ( "load" , resolve , { once : true } ) ;
link . addEventListener ( "error" , reject , { once : true } ) ;
document . head . appendChild ( link ) ;
} ) ;
}
function parseMarkdown ( source ) {
if ( window . marked && typeof window . marked . parse === "function" ) {
return window . marked . parse ( source ) ;
}
if ( window . marked && window . marked . marked && typeof window . marked . marked . parse === "function" ) {
return window . marked . marked . parse ( source ) ;
}
throw new Error ( "Marked unavailable" ) ;
}
function markdownDocument ( body ) {
var theme = currentTheme ( ) ;
var base = '<base href="' + escapeAttribute ( new URL ( state . sourceURL , window . location . href ) . href ) + '">' ;
return '<!doctype html><html data-theme="' + escapeAttribute ( theme ) + '"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">' +
base +
'<link rel="stylesheet" href="/static/css/80-markdown-preview.css">' +
'<style>' + markdownThemeStyle ( theme ) + '</style>' +
'</head><body><main>' + body + '</main></body></html>' ;
}
function markdownThemeStyle ( theme ) {
var themes = {
revamp : [ "dark" , "#0b0b16" , "#f5f3ff" , "#aaa4d6" , "#17142d" , "#211b3e" , "rgba(168,150,255,0.24)" , "#67e8f9" , "#a78bfa" , "#1b1724" , "#0f111a" , "#f8fafc" , "rgba(248,250,252,0.16)" , "rgba(0,0,0,0.28)" , "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif" , "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace" ] ,
classic : [ "dark" , "#09090b" , "#fafafa" , "#a1a1aa" , "#18181b" , "#27272a" , "rgba(255,255,255,0.13)" , "#e4e4e7" , "#d4d4d8" , "#111113" , "#09090b" , "#fafafa" , "rgba(250,250,250,0.15)" , "rgba(0,0,0,0.3)" , "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif" , "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace" ] ,
retro : [ "light" , "#c0c0c0" , "#000000" , "#404040" , "#ffffff" , "#dfdfdf" , "#000000" , "#000078" , "#000078" , "#ffffff" , "#000000" , "#f5f5f5" , "#808080" , "transparent" , "\"PixeloidSans\",\"PixelOperator\",\"Microsoft Sans Serif\",Tahoma,sans-serif" , "\"PixelOperatorMono\",Consolas,monospace" ] ,
gruvbox : [ "dark" , "#1d2021" , "#ebdbb2" , "#bdae93" , "#282828" , "#32302f" , "rgba(235,219,178,0.2)" , "#fabd2f" , "#d79921" , "#1b1d1e" , "#161819" , "#fbf1c7" , "rgba(251,241,199,0.18)" , "rgba(0,0,0,0.26)" , "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif" , "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace" ] ,
cyberpunk : [ "dark" , "#08070d" , "#fff36f" , "#9bfaff" , "#16131f" , "#251d34" , "rgba(255,242,0,0.34)" , "#00f0ff" , "#ff2a6d" , "#100d18" , "#07060b" , "#f8fafc" , "rgba(0,240,255,0.26)" , "rgba(255,42,109,0.14)" , "Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif" , "Consolas,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace" ]
} ;
var t = themes [ theme ] || themes . revamp ;
var vars = "color-scheme:" + t [ 0 ] + ";--md-bg:" + t [ 1 ] + ";--md-fg:" + t [ 2 ] + ";--md-muted:" + t [ 3 ] + ";--md-panel:" + t [ 4 ] + ";--md-panel-2:" + t [ 5 ] + ";--md-border:" + t [ 6 ] + ";--md-link:" + t [ 7 ] + ";--md-accent:" + t [ 8 ] + ";--md-code-bg:" + t [ 9 ] + ";--md-block-code-bg:" + t [ 10 ] + ";--md-block-code-fg:" + t [ 11 ] + ";--md-block-code-border:" + t [ 12 ] + ";--md-shadow:" + t [ 13 ] + ";--md-font:" + t [ 14 ] + ";--md-mono:" + t [ 15 ] + ";" ;
return ":root{" + vars + "}*{box-sizing:border-box}html,body{min-height:100%;margin:0;background:var(--md-bg);color:var(--md-fg);font-family:var(--md-font)}body{padding:clamp(1rem,4vw,2.25rem);font-size:16px;line-height:1.65}main{max-width:54rem;margin:0 auto;padding:clamp(1rem,3vw,2rem);border:1px solid var(--md-border);border-radius:10px;background:var(--md-panel);box-shadow:0 20px 60px var(--md-shadow)}a{color:var(--md-link)}h1,h2,h3,h4,h5,h6{color:var(--md-fg);line-height:1.2}code,kbd,pre{font-family:var(--md-mono)}pre{overflow:auto;padding:1rem;border:1px solid var(--md-block-code-border)!important;background:var(--md-block-code-bg)!important;color:var(--md-block-code-fg)!important}code{background:var(--md-code-bg);border-radius:4px;padding:.12rem .3rem}pre code,pre>code,pre code[class*=\"language-\"]{padding:0!important;background:transparent!important;color:inherit!important;border:0!important}blockquote{margin:1rem 0;padding:.2rem 1rem;border-left:3px solid var(--md-accent);color:var(--md-muted);background:var(--md-panel-2)}table{width:100%;border-collapse:collapse;display:block;overflow:auto}th,td{border:1px solid var(--md-border);padding:.5rem .7rem}hr{border:0;border-top:1px solid var(--md-border)}img,video{max-width:100%;height:auto}" ;
}
function currentTheme ( ) {
var theme = document . documentElement . dataset . theme || "revamp" ;
return /^(revamp|classic|retro|gruvbox|cyberpunk)$/ . test ( theme ) ? theme : "revamp" ;
}
function withBaseElement ( source ) {
var base = '<base href="' + escapeAttribute ( new URL ( state . sourceURL , window . location . href ) . href ) + '">' ;
if ( /<head[\s>]/i . test ( source ) ) {
return source . replace ( /<head([^>]*)>/i , "<head$1>" + base ) ;
}
return base + source ;
}
function escapeAttribute ( value ) {
return value . replace ( /&/g , "&" ) . replace ( /"/g , """ ) . replace ( /</g , "<" ) ;
}
function formatJSON ( source ) {
try {
return JSON . stringify ( JSON . parse ( source ) , null , 2 ) ;
} catch ( error ) {
return source ;
}
}
function extensionFor ( name ) {
var parts = name . toLowerCase ( ) . split ( "." ) ;
return parts . length > 1 ? parts . pop ( ) : "" ;
}
2026-06-08 03:43:43 +03:00
function isArchiveFile ( extension , baseType ) {
var archiveExtensions = {
"apk" : true ,
"docx" : true ,
"ear" : true ,
"epub" : true ,
"jar" : true ,
"pptx" : true ,
"war" : true ,
"xlsx" : true ,
"zip" : true
} ;
var archiveTypes = {
"application/epub+zip" : true ,
"application/java-archive" : true ,
"application/vnd.android.package-archive" : true ,
"application/x-zip-compressed" : true ,
"application/zip" : true
} ;
return Boolean ( archiveExtensions [ extension ] || archiveTypes [ baseType ] ) ;
}
2026-06-03 14:28:50 +03:00
function languageFor ( extension , baseType ) {
var extensionMap = {
"c" : "c" ,
"cc" : "cpp" ,
"conf" : "nginx" ,
"cpp" : "cpp" ,
"cs" : "csharp" ,
"css" : "css" ,
"csv" : "csv" ,
"diff" : "diff" ,
"dockerfile" : "docker" ,
"go" : "go" ,
"h" : "c" ,
"hpp" : "cpp" ,
"htm" : "html" ,
"html" : "html" ,
"ini" : "ini" ,
"java" : "java" ,
"js" : "javascript" ,
"json" : "json" ,
"jsx" : "jsx" ,
"kt" : "kotlin" ,
"log" : "log" ,
"lua" : "lua" ,
"md" : "markdown" ,
"mdown" : "markdown" ,
"markdown" : "markdown" ,
"php" : "php" ,
"pl" : "perl" ,
"properties" : "properties" ,
"py" : "python" ,
"rb" : "ruby" ,
"rs" : "rust" ,
"sh" : "bash" ,
"sql" : "sql" ,
"swift" : "swift" ,
"toml" : "toml" ,
"ts" : "typescript" ,
"tsx" : "tsx" ,
"txt" : "text" ,
"xml" : "xml" ,
"yaml" : "yaml" ,
"yml" : "yaml" ,
"zig" : "zig"
} ;
var typeMap = {
"application/javascript" : "javascript" ,
"application/json" : "json" ,
"application/ld+json" : "json" ,
"application/markdown" : "markdown" ,
"application/xml" : "xml" ,
"application/x-httpd-php" : "php" ,
"application/x-sh" : "bash" ,
"image/svg+xml" : "xml" ,
"text/css" : "css" ,
"text/csv" : "csv" ,
"text/html" : "html" ,
"text/javascript" : "javascript" ,
"text/markdown" : "markdown" ,
"text/plain" : "text" ,
"text/x-go" : "go" ,
"text/xml" : "xml"
} ;
if ( extensionMap [ extension ] ) {
return extensionMap [ extension ] ;
}
if ( typeMap [ baseType ] ) {
return typeMap [ baseType ] ;
}
if ( baseType . indexOf ( "+json" ) !== - 1 ) {
return "json" ;
}
if ( baseType . indexOf ( "+xml" ) !== - 1 ) {
return "xml" ;
}
if ( baseType . indexOf ( "text/" ) === 0 ) {
return "text" ;
}
return "" ;
}
} ) ( ) ;