NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
/* eslint-disable no-mixed-spaces-and-tabs */
/* eslint-disable no-useless-escape */
/* eslint-disable no-fallthrough */
/* eslint-disable no-case-declarations */
/* eslint-disable indent */
/* eslint-disable quotes */
/* jshint esversion: 6 */
// ==UserScript==
// @name Supercharged Local Directory File Browser
// @version 7.0.1
// @description Makes directory index pages (either local or remote open directories) actually useful. Adds sidebar and content preview pane; keyboard navigation; sorting; light/dark UI; preview images/fonts in navigable grids; browse subdirectories w/o page reload; media playback, shuffle/loop options; basic playlist (m3u, extm3u) & cuesheet* (with .cuetxt ext) support; create, edit, preview, save markdown/plain text files; open font files, view complete glyph repertoire, save glyphs as .svg; more.
// @author gaspar_schot
// @license GPL-3.0-or-later
// @homepageURL https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser
// @contributionURL https://paypal.me/mschrauzer
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUmRcmZzP8zmf8pVcWPAAAAAXRSTlMAQObYZgAAAFBJREFUeF7tyqERwDAMBEE3mX5UiqDmqwwziTPHjG7xrmzrLFtRaApDIRiKQlMYCsFQFJrCUAiGotAU5hTA1WB4fhkMBsOJwWAwgHvB8CHpBcTbpxy4RZNvAAAAAElFTkSuQmCC
// @include file://*
// @include about:blank
// @include https://www.example.com/path/to/directory/*
// @require https://cdn.jsdelivr.net/npm/markdown-it@12.3.2/dist/markdown-it.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-footnote@3.0.2/dist/markdown-it-footnote.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-toc-done-right@2.1.0/dist/markdown-it-toc-made-right.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-sub@1.0.0/dist/markdown-it-sub.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-sup@1.0.0/dist/markdown-it-sup.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-deflist@2.0.3/dist/markdown-it-deflist.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-multimd-table@4.0.2/dist/markdown-it-multimd-table.min.js
// @require https://cdn.jsdelivr.net/npm/markdown-it-center-text@1.0.4/dist/markdown-it-center-text.min.js
// @require https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js
// @updateURL https://openuserjs.org/meta/gaspar_schot/Supercharged_Local_Directory_File_Browser.meta.js
// ==/UserScript==
(function() {
'use strict';
// ***** USER SETTINGS ***** //
const $settings = {
// NOTE: These settings will be overwritten whenever the script is updated. Use the "Export User Settings" menu item to save them.
// You can paste the exported settings between the two lines below:
//--------------------------------------------------------//
bookmarks: // N.B.: Directory links must end with "/", file links must end with another character.
// You may add as many menus and links as you like; just copy the example below and edit as needed.
// Local directory bookmarks must begin with "file:///"; external bookmarks must begin with the correct protocol ("http://" or "ftp://", etc.).
[
// { 'menu_title':'My Sample Menu',
// 'links': [
// { 'link_name':'My Directory Link 1', 'link':'file:///Path/To/My/Directory/' },
// { 'link_name':'My Directory Link 2', 'link':'file:///Path/To/My/Directory_2/' },
// { 'link_name':'My External Link', 'link':'https://www.mywebpage.com/' },
// { 'link_name':'My File Link', 'link':'file:///Path/To/My/File.ext' },
// ]},
{ 'menu_title':'My Sample Bookmark Menu',
'links': [
{ 'link_name':'My Directory Link 1', 'link':'file:///Path/To/My/Directory/' },
{ 'link_name':'My Directory Link 2', 'link':'file:///Path/To/My/Directory_2/' },
{ 'link_name':'My External Link', 'link':'https://www.mywebpage.com/' },
{ 'link_name':'My File Link', 'link':'file:///Path/To/My/File.ext' },
]},
],
// GENERAL USER SETTINGS (Note: any changes you make will be reset to defaults every time the script is updated)
alternate_background: true, // If true (default true), alternate sidebar row background color.
apps_as_dirs: true, // Un*x/Mac OS only: if true, treat apps as directories; allows app contents to be browsed. This is the default behavior for Chrome.
// If false, treat apps as ignored files.
autoload_media: true, // If true (default), the first audio or video file found in a directory will be automatically selected and loaded for playback.
// Also, cover art files (if any) will be loaded in the preview pane.
// Files with 'cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes' in title will be loaded first, in that order, with exact matches having preference.
// Note that there can be false positives because a file will be matched whenever there is no exact match and one of these words appears in an image name.
// Otherwise the first image file in directory will be loaded.
autoload_index_files: true, // If true, automatically select first "index.xxx" (.xxx !== .htm) file found in directory.
// Note: the browser will automatically load any index.html files it finds in the directory, so the script will not work properly in such cases.
theme: 'light', // Options: 'light' or 'dark'
sort_by: 'default', // Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
// default = Chrome sorting: dirs on top, files alphabetical.
sort_direction: 'sort_ascending', // Choose from: 'sort_ascending' (A-Z) [default] or 'sort_descending' (Z-A).
dirs_on_top: false, // If true, directories will always be listed firs except when sorting by "name" (since otherwise sorting by "name" would equal "default").
// If false (default), directories and files will be sorted together. (In practice, dirs will typically still be separated when sorting by size, kind, and extension.)
grid_font_size: 1, // Default = 1
grid_image_size: 184, // Default = 184 (200px - 16px)
show_details: true, // If true (default), hide file and directory details; if false, show them.
hide_ignored_items: false, // If true, ignored files (= files the browser cannot natively open, e.g., common office and graphics files) will be hidden.
// If false (default), ignored files will appear greyed-out.
ignore_ignored_items: true, // If true (default), clicking ignored file types (see $row_settings below) in the directory list will not cause the browser to attempt to open the file.
// It is recommended to leave the default unchanged.
// Ignored items can still be downloaded with ctrl-click or right-click.
show_invisibles: true, // Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
show_numbers: true, // If true (default true), number index items
show_image_thumbnails: true, // If true, images thumbnails will be shown instead of the default icon; this can slow page load.
UI_font: 'system-ui, sans-serif', // Choose an installed font for the UI; if undefined, use browser defaults instead. [system-ui, sans-serif]
UI_font_size: '13px', // Choose a default UI font size; use any standard CSS units.
use_custom_icons: true, // if true (default), use custom icons for dirs and files
// if false, use browser/server default icons
// TEXT EDITING SETTINGS
enable_text_editing: true, // If true (default), allow plain text files to be edited.
text_editor_theme: 'default', // Options: 'default' = use "theme" setting, else always 'light' or 'dark'.
text_editor_default_view: 'text_editor_preview', // Options: 'text_editor_preview','text_editor_html'
text_editor_split_view: true, // If true, show split view on plain text file load.
// if false (default), use default text_editor_preview setting.
text_editor_sync_scroll: true, // If true (default: true), show split view on plain text file load
// if false, use default text_editor_preview setting.
hide_sidebar: false, // Show or hide sidebar by default
play_all_media: true // Continuous playback of all media files (i.e., both audio and video), not just selected media type.
//--------------------------------------------------------//
// Paste your exported settings between the above two lines.
};
// $ITEM_KIND:
// DO NOT DELETE ANY EXISTING CATEGORIES!
// Add file extensions for sorting and custom icon display to the existing categories.
// You can also define your own new categories, but do not add an extension to more than one row_type category.
// Do not add leading "." to the extensions.
const $item_kind = {
// myRowType: ['ext1','ext2'],
dir: ['/'], // loaded in iframe#content_iframe
app: ['app/','app','bat','cgi','com','exe','jar','msi','wsf'], // generally ignored; apps may be opened as directories
alias: ['alias','desktop','directory','lnk','symlink','symlink/'],
archive: ['7z','archive','b6z','bin','bzip','bz2','cbr','dmg','gz','iso','mpkg','pkg','rar','sit','sitx','tar','tar.gz','zip','zipx','zxp'], // ignored
audio: ['aac','aif','aiff','ape','flac','m4a','mka','mp3','ogg','opus','wav'], // loaded in audio#audio
bin: ['a','bundle','dll','dyld','dylib','gem','icc','msi','pyc','pyo','o','rakefile','ri','so','xml','2'], // ignored
code: ['bak','bash','bash_profile','bashrc','c','cfg','cnf','codes','coffee','conf','csh','cshrc','cson','css','cuetxt','custom_aliases','d','default','description','dist','editorconfig','emacs','example','gemspec','gitconfig','gitignore','gitignore_global','h','hd','ini','js','json','jsx','less','list','local','login','logout','lua','mkshrc','old','pc','php','pl','plist','pre-oh-my-zsh','profile','pth','py','rb','rc','rdoc','sass','settings','sh','strings','taskrc','tcl','viminfo','vimrc','vue','yaml','yml','zlogin','zlogout','zpreztorc','zprofile','zsh','zshenv','zshrc'], // treated as text, opened in iframe#content_iframe text editor
database: ['accdb','db','dbf','mdb','pdb','sql', 'sqlite','sqlitedb','sqlite3'], // ignored
ebook: ['azw','azw1','azw3','azw4','epub','ibook','kfx','mobi','tpz'], // ignored
font: ['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'], // opened in div#content_font
graphics: ['afdesign','afpub','ai','book','dtp','eps','fm','icml','idml','indd','indt','inx','mif','pmd','pub','qxb','qxd','qxp','sla','swf','ai','arw','cr2','dng','eps','jpf','nef','psd','psd','raw','tif','tiff'], // ignored
htm: ['htm','html','xhtm','xhtml'], // opened in iframe#content_iframe
image: ['apng','bmp','gif','ico','jpeg','jpg','png','svg','webp'],
link: ['url','webloc','inetloc'],
markdown: ['md','markdown','mdown','mkdn','mkd','mdwn','mdtxt','mdtext'], // treated as text, opened in iframe#content_iframe text editor
other_ignored: ['alias','cue','xmp'],
office: ['csv','doc','docx','epub','key','numbers','odf','ods','odt','pages','rtf','scriv','wpd','wps','xlr','xls','xlsx','xlm'], // ignored
playlist: ['m3u','m3u8','pls','asx','wpl','xspf'],
pdf: ['pdf'], // open in embed#content_pdf
system: ['DS_Store','ds_store','icon','ics','spotlight-v100/','temporaryitems/','documentrevisions-v100/','trashes/','fseventsd/','dbfseventsd','file','localized','programdata'], // ignored system items
text: ['log','nfo','txt','readme'], // opened in iframe#content_iframe text editor
video: ['m4v','mkv','mov','mp4','mpeg','webm'] // loaded in video#content_video
};
// $ROW_SETTINGS: Ignore or Exclude files by extension
const $row_settings = {
// ignored: $item_kind or files with extensions added here will not be loaded if selected in the sidebar (prevents the browser from attempting to download the file).
ignored: $item_kind.archive.concat( $item_kind.bin, $item_kind.database, $item_kind.graphics, $item_kind.other_ignored, $item_kind.office, $item_kind.playlist, $item_kind.system)
};
// ***** END USER SETTINGS ***** //
// ************ J + M + J ************* //
// ************************************ //
// DON'T EDIT ANYTHING BELOW THIS LINE. //
// ************************************ //
// ***** UTILITIES ***** //
function loadFileURL() { // ===> LOAD FILE URL
// if window.location points to a file, change window location to file container dir, add search_param of file name; then load file container directory and load file in content pane.
let search_params = getSearchParams();
search_params.set( 'file', window.location.pathname.split('/').reverse()[0]);
window.location = window.location.pathname.slice( 0,window.location.pathname.lastIndexOf('/') ) +'/?'+ search_params ;
return;
}
if ( !window.location.pathname.endsWith('/') && window.top === window.self ) { loadFileURL(); } // load file urls
//==============================//
function isTopWindow() { return ( window.top === window.self || false ) } // ===> TOP WINDOW OR IFRAME
function getBrowser() { //*** needs testing for new userAgentData object --> what are possible brand names?; combine with getOS() // ===> GET BROWSER
let brand = ( navigator.userAgentData !== undefined ? navigator.userAgentData.brands[0].brand.toLowerCase() : navigator.userAgent );
switch(true) {
case brand === 'chromium' || ( /chrome/.test(brand) ): return 'is_chrome';
case brand === 'firefox' || ( !/chrome/.test(brand) ): return 'is_gecko';
case brand === 'msie' || ( /msie/.test(brand) ): return 'is_explorer';
// case brand === 'edge' || ( /edge/.test(brand) ): return 'is_edge'; // need case for ms edge
case brand === 'opera' || ( /opera/.test(brand) ): return 'is_opera';
case brand === 'safari' || ( /safari/.test(brand) ): return 'is_safari';
}
}
function getOS() { // modded from https://***stackoverflow.com/questions/38241480/detect-macos-ios-windows-android-and-linux-os-with-js // ===> GET OS
var platform = window.navigator.platform, macos_platforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], windows_platforms = ['Win32', 'Win64', 'Windows', 'WinCE'], os = null;
switch(true) {
case macos_platforms.indexOf(platform) !== -1: os = 'macos'; break;
case windows_platforms.indexOf(platform) !== -1: os = 'windows'; break;
// case iosPlatforms.indexOf(platform) !== -1: os = 'ios'; break; // just in case;
// case /Android/.test(userAgent): os = 'android'; break; // just in case;
case !os && /Linux/.test(platform): os = 'linux'; break;
}
return os;
}
function newURL(link) { // ===> NEW URL
try { return new URL(link,document.baseURI); }
catch(error) { return; } //console.log('This link is invalid. Please check the file.'); }
}
function decodeURIComponentSafe(str) { // ===> DECODE URI COMPONENT SAFE
if ( !str ) { return str; }
try { // Fix "%" error in file name; see https://***stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser ***//
return decodeURIComponent(str.replace(/%(?![0-9a-fA-F]{2})/g,'%25') ).replace(/\"/g,'\"'); // replace % with %25 if not followed by two a-f/number; replace " with html entity
} catch(e) {
return str;
}
}
function escapeStr(str) { str = str.replace(/([$?*+()[]|^])/g,'\\$1'); return str; } // ===> ESCAPE STRING
//==============================//
const $protocol = window.location.protocol; // GLOBAL: protocol
const $origin = $protocol +'//'+ window.location.host; // GLOBAL: origin
let current_location = decodeURIComponentSafe( [location.protocol, '//', location.host, location.pathname].join('') ); // GLOBAL: current location
const current_dir_path = current_location.replace(/([/|_|—])/g,'$1<wbr>').replace(/\\/g,'/'); // GLOBAL: current dir path w/o query string for display
const current_dir = current_location.split('/').slice(-2,-1).toString(); // GLOBAL: current dir
//==============================//
function getSearchParams() { return new URL(window.location).searchParams; } // ===> GET SEARCH PARAMS
function getSearchParam(key) { // ===> GET SEARCH PARAM value by key
let search_params = getSearchParams(), value = '';
switch(true) {
case key === 'width': // width: set the stored sidebar width or use default 30%
value = ( !search_params.has(key) || window.innerWidth === 0 ? 30 : Math.round(100 * Number.parseInt(search_params.get('width'))/window.innerWidth) ); // percentage
break;
default: // if query_string has key/value pair, use it, else use key/value pair from $settings
value = ( search_params.has(key) ? search_params.get(key) : $settings[key] !== undefined ? $settings[key].toString() : '' );
value = value.replace('%2F','').replace('/',''); // some servers add a '/' to end of query string
}
return value;
}
function setSearchParam(key, value) { let search_params = getSearchParams(); search_params.set( key, value ); updateSearchParams(search_params); } // ===> SET SEARCH PARAM
function removeSearchParam(key) { let search_params = getSearchParams(); search_params.delete(key); updateSearchParams(search_params); } // ===> REMOVE SEARCH PARAM
function makeSearchParams(params) { // ===> MAKE SEARCH PARAMS
let query_str = new URLSearchParams();
for ( let param of params ) { query_str.append(param,getSearchParam(param)); }
return query_str; // return query_str
}
function toggleSearchParam(key) { // ===> TOGGLE SEARCH PARAM by key
let search_params = getSearchParams();
let non_bool_prefs = { // 'body_class,data-ui_pref': {'pref_name':'value'}
'theme': {'theme':(getSearchParam('theme') === 'light' ? 'dark' : 'light') },
'theme_light': {'theme':'light'},
'theme_dark': {'theme':'dark'},
'text_editor_theme': {'text_editor_theme':(getSearchParam('text_editor_theme') === 'light' ? 'dark' : 'light') },
'text_editor_theme_default': {'text_editor_theme':getSearchParam('theme') },
'text_editor_theme_light': {'text_editor_theme':'light'},
'text_editor_theme_dark': {'text_editor_theme':'dark'},
'text_editor_raw': {'text_editor_default_view':getSearchParam('text_editor_default_view')},
'text_editor_preview': {'text_editor_default_view':'text_editor_preview'},
'text_editor_html': {'text_editor_default_view':'text_editor_html'},
'sort_by_name': {'sort_by':'name'},
'sort_by_default': {'sort_by':'default'},
'sort_by_duration': {'sort_by':'duration'},
'sort_by_size': {'sort_by':'size'},
'sort_by_date': {'sort_by':'date'},
'sort_by_kind': {'sort_by':'kind'},
'sort_by_ext': {'sort_by':'ext'},
// 'sort_by_duration': {'sort_by':'time'}, // don't add sort by time because times aren't available on initial page load
'sort_ascending': {'sort_direction':'sort_ascending'}, // sort A-Z
'sort_descending': {'sort_direction':'sort_descending'}, // sort Z-A
};
var value, param_value, settings_value;
switch(true) {
case non_bool_prefs[key] !== undefined:
value = Object.values( non_bool_prefs[key] ).toString(); // get the value for the key
key = Object.keys(non_bool_prefs[key]).toString(); // must come after value: i.e., don't redefine key before getting value
setSearchParam( key,value ); break; // if the setting[key] = value, delete from search_params; else set query_pref
default: // boolean prefs
settings_value = $settings[key]; // get the default pref value from $settings
param_value = search_params.get(key); // see if pref is set in query prefs
// key = key.replace('toggle_','');
value = ( param_value === null ? settings_value.toString() : param_value.toString() ); // if pref not set in queries, use default value; else use query value
value = ( value === 'true' ? 'false' : 'true' ); // toggle value
if ( ( param_value !== null && param_value !== settings_value ) ) { removeSearchParam( key ); } else { setSearchParam( key,value ); }
}
}
function updateSearchParams(search_params) { // ===> UPDATE SEARCH PARAMS
let search_params_str = search_params.toString().replace('%2F','').replace('/','');
let new_location = ( search_params_str.length === 0 ? window.location.pathname : window.location.pathname +'?'+ search_params_str ); // don't add ? if no search params
if ( search_params_str.length > 0 ) { window.history.replaceState({}, document.title, new_location); }
if ( isTopWindow() ) { updateParentLinks(); }
}
//==============================//
function hasClass(elSelector,className) { let el = document.querySelector(elSelector); return ( el === undefined || el === null ? false : el.classList.contains(className) ); } // ===> HAS CLASS
function addClass(elSelector,className) { // ===> ADD CLASS
let els = Array.from(document.querySelectorAll(elSelector));
if ( els !== undefined && els !== null ) { els.forEach( el => el.classList.add(...className.replace(/\s{2,}/g,' ').split(' ')) ); }
}
function removeClass(elSelector,className) { // ===> REMOVE CLASS; if no className provided, remove all classes
let classes = (className !== undefined ? className.replace(/\s{2,}/g,' ').split(' ') : '');
let els = Array.from(document.querySelectorAll(elSelector));
if ( els !== undefined && els !== null ) {
switch(true) {
case className === undefined: case className === '': els.forEach( el => el.removeAttribute('class') ); break;
default: els.forEach( el => el.classList.remove(...classes) ); break;
}
}
}
function addRemoveClassSiblings(elSelector,className) { // ===> ADD/REMOVE CLASS SIBLINGS, add to elements
let el = document.querySelector(elSelector);
if ( el !== undefined && el !== null ) {
let siblings = el.parentElement.children;
Array.from(siblings).forEach( sibling => sibling.classList.remove(...className.replace(/\s{2,}/g,' ').split(' ') ) ); // remove class name from els
addClass(elSelector,className); // restore class name to selected el
}
}
//==============================//
function getAttribute(elSelector,attributeName) { let el = document.querySelector(elSelector); if ( el !== null ) { return el.getAttribute(attributeName); } } // ===> GET ATTRIBUTE
function setAttribute(elSelector,attributeName,value) { let el = document.querySelector(elSelector); if ( el !== null ) { el.setAttribute(attributeName,value); } } // ===> SET ATTRIBUTE
function removeAttribute(elSelector,attributeNamesArr) { // ===> REMOVE ATTRIBUTE
let els = document.querySelectorAll(elSelector); if ( els !== null ) { Array.from(els).forEach( el => attributeNamesArr.forEach( attributeName => el.removeAttribute(attributeName) ) ); }
}
function getContentPaneData() { return ( document.getElementById('content_pane') !== null ? getAttribute('#content_pane','data-content') : ''); } // ===> GET CONTENT_PANE DATA content
function getVisibleElsBySelector(selector) { // ===> GET VISIBLE ELS BY SELECTOR
let els = Array.from( document.querySelectorAll(selector) ).filter( function(el) {
let el_styles = window.getComputedStyle(el); // get el styles
return ( el_styles.getPropertyValue('display') !== 'none' || ( el.offsetWidth > 0 || el.offsetHeight > 0 ) ); // return els with display:none or zero width/height
});
return els;
}
//==============================//
function dblclick(el,func) { var evt = new MouseEvent('dblclick'); el.addEventListener(evt,func); el.dispatchEvent(evt); } // ===> DOUBLE CLICK
function altKey(e) { return ( !e.metaKey && !e.ctrlKey && e.altKey && !e.shiftKey ); } // ===> ALT KEY test
function altShiftKey(e) { return ( !e.metaKey && !e.ctrlKey && e.altKey && e.shiftKey ); } // ===> ALT SHIFT KEY test
function cmdKey(e) { return ( (e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey ); } // ===> CMD/CTRL KEY test
function cmdAltKey(e) { return ( (e.metaKey || e.ctrlKey) && e.altKey && !e.shiftKey ); } // ===> CMD/CTRL ALT KEY test
function cmdShiftKey(e) { return ( (e.metaKey || e.ctrlKey) && !e.altKey && e.shiftKey ); } // ===> CMD/CTRL SHIFT KEY test
// function cmdAltShiftKey(e) { return ( (e.metaKey || e.ctrlKey) && e.altKey && e.shiftKey; } // ===> CMD/CTRL ALT SHIFT KEY test (not used)
//==============================// OPEN/SAVE FILES
function openFile(e,type) { // type: font or playlist. // ===> OPEN FILE
removeClass('body','has_menu faded');
if (window.File && window.FileReader && window.FileList && window.Blob) {
let files = e.target.files[0];
let reader = new FileReader();
if ( type === 'font' ) { reader.readAsArrayBuffer(files); }
if ( type === 'playlist' ) { reader.readAsText(files); }
reader.onload = function() {
if ( type === 'font' ) { openFontFile(files,reader); }
if ( type === 'playlist' ) { openPlaylist(files.name,'',reader.result); }
return true;
};
} else {
alert('Can\'t open file: file APIs are not fully supported in this browser.');
}
}
function saveFile(content,mimetype,file_name) { // ===> SAVE FILE
let blob = new Blob([content], {type: mimetype});
let $download_el = window.document.createElement('a');
$download_el.style = "display:none";
$download_el.href = window.URL.createObjectURL(blob);
$download_el.download = file_name;
document.body.appendChild($download_el);
$download_el.click();
document.body.removeChild($download_el);
URL.revokeObjectURL(blob);
}
// END UTILITIES
//==============================//
// ***** SET UP UI ELEMENTS ***** //
function updateParentLinkSearchParams(str) { //*** decrement selected and history values ***// // ===> UPDATE PARENT LINK SEARCH PARAMS
let query_str = new URLSearchParams(str); // make new search params from window.location.search
let history = ( query_str.has('history') ? query_str.get('history') : undefined );
switch(true) {
case history !== undefined:
history = history.split(' ');
switch(true) {
case history.length > 1: query_str.set('selected',history[0]); history.shift(); query_str.set('history',history.join('+')); break;
case history.length === 1: query_str.set('selected',history[0]); history.shift(); query_str.delete('history'); break;
}
break;
default: query_str.delete('selected');
}
return decodeURIComponentSafe(query_str.toString());
}
function createParentLinks() { // ===> CREATE PARENT LINKS
let link, links = [], query_str = window.location.search.toString().slice(1);
let link_pieces = current_location.split('/'); // make array of parent directories
link_pieces = link_pieces.slice(2,-2); // remove beginning and ending empty elements and current directory
while ( link_pieces.length > 0 ) { // while there are link pieces...
query_str = updateParentLinkSearchParams(query_str); // update selected and history
link = $protocol +'//'+ link_pieces.join('/') + '/?' + query_str; // assemble link
links.push(link); // add to link array
link_pieces.pop(); // remove last link piece and repeat...
}
return links;
}
function createParentLinkItems() { // ===> CREATE PARENT LINK ITEMS
let parent_link_menu_items = [], links = createParentLinks();
for ( let i = 0; i < links.length; i++ ) {
let display_name = links[i].split('/?')[0];
display_name = display_name.replace(/\//g,'\/<wbr>');
let menu_item = `<li><a href="${ links[i] }" class="display_block">${ display_name }/</a></li>`;
parent_link_menu_items.push(menu_item);
}
let parent_link = ( links[0] === undefined ? window.location.href : links[0])
return [parent_link_menu_items.join(''),parent_link]; // return parents link items
}
function updateParentLinks() { let links = createParentLinkItems(); document.getElementById('parents_links').innerHTML = links[0]; document.querySelector('#parent_dir_nav a').href = links[1]; } // ===> UPDATE PARENT LINKS
function bookmarksMenuItems() { // ===> BOOKMARK MENU ITEMS: create user bookmarks from $setting
const bookmarks = $settings.bookmarks;
let menu_items = [], links_arr = [], links_arr_str = '', links;
if ( bookmarks.length > 0 ) {
for ( let i = 0; i < bookmarks.length; i+=1 ) {
links = bookmarks[i].links;
for ( let j = 0; j < links.length; j+=1 ) {
if ( !links[j].link.endsWith('/') ) {
links[j].link = links[j].link.slice(0,links[j].link.lastIndexOf('/') + 1) + '?file=' + links[j].link.slice(links[j].link.lastIndexOf('/') + 1) ;
}
links[j].link_name = links[j].link_name.split('/').join('/<wbr>');
links_arr[j] = `<li class="is_submenu"><a class="menu_item has_icon_before" href="${ links[j].link }">${ links[j].link_name }</a></li>`;
}
links_arr_str = links_arr.join('');
menu_items[i] = ` <li class="bookmark has_submenu" title="User bookmarks (added in code @ $settings)."><span class="menu_item">${ bookmarks[i].menu_title }</span>${SVG_UI_Icons['arrow']}
<ul class="submenu background_grey_85 border_all">${ links_arr_str }</ul>
</li>`;
}
menu_items = menu_items.join('');
}
return menu_items;
}
//==============================//
// SVG UI ICONS
const SVG_UI_Icons = {
'arrow': '<svg viewBox=\'0 0 20 20\' xmlns=\'http://www.w3.org/2000/svg\' class=\'invert\'><path fill=\'%23888\' fill-opacity=\'.75\' d=\'m4 4 12 6-12 6z\'/></svg>',
'bookmark': '<svg viewBox=\'0 0 20 20\' xmlns=\'http://www.w3.org/2000/svg\' class=\'invert\'><path fill=\'%23888\' d=\'m2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4zm2 0v15l6-3 6 3v-15z\'/></svg>',
'check_mark': '<svg viewBox=\'0 0 12 9\' xmlns=\'http://www.w3.org/2000/svg\'><path fill=\'currentColor\' d=\'m-.071 10.929 2.571-2.571 4.5 4.499 10.285-10.285 2.571 2.572-12.856 12.856z\' transform=\'matrix(.55 0 0 .55 .578932 -1.01245)\'/></svg>',
'chevron': '<svg viewBox=\'0 0 24 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0v14h3v-11h11v-3z\' transform=\'matrix(.707107 .707107 -.707107 .707107 11.8995 1)\'/></svg>',
'document': '<svg viewBox=\'0 0 20 20\' xmlns=\'http://www.w3.org/2000/svg\'><path fill=\'%23222222\' d=\'M4 18h12V6h-4V2H4v16zm-2 1V0h12l4 4v16H2v-1z\' /></svg>',
'error': '<svg viewBox=\'0 0 20 20\' xmlns=\'http://www.w3.org/2000/svg\'><g fill-opacity=\'.75\'><path fill=\'%23444\' d=\'m1.075 18.05 8.146-16.683c.236-.484.924-.491 1.169-.011l8.537 16.683c.223.435-.093.952-.582.952h-16.683c-.483 0-.799-.507-.587-.941z\' fill=\'%23ffb636\'/><path d=\'m11.055 7.131-.447 6.003c-.034.45-.425.787-.874.753-.408-.03-.724-.356-.753-.753l-.447-6.003c-.052-.696.47-1.302 1.167-1.354.696-.052 1.302.47 1.354 1.166.005.061.004.129 0 .188zm-1.26 8.037c-.641 0-1.159.518-1.159 1.158 0 .641.518 1.159 1.159 1.159.64 0 1.158-.518 1.158-1.159 0-.64-.518-1.158-1.158-1.158z\'/></g></svg>',
'folder': '<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m0 4c0-1.1.9-2 2-2h7l2 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-16a2 2 0 0 1 -2-2zm2 2v10h16v-10z\'/></svg>',
'grid': '<svg viewBox=\'0 0 20 20\'><path fill=\'currentColor\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
'menu': '<svg viewBox=\'0 0 13 10\'><g fill=\'%23222\'><path d=\'m0 0h13v2h-13z\'/><path d=\'m0 4h13v2h-13z\'/><path d=\'m0 8h13v2h-13z\'/></g></svg>',
'minus': '<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m1 8h18v4h-18z\'/></svg>',
'multiply': '<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m10 7 6-6 3 3-6 6 6 6-3 3-6-6-6 6-3-3 6-6-6-6 3-3z\'/></svg>',
'music': '<svg viewBox=\'0 0 20 20\'><path fill=\'%23888\' fill-opacity=\'.4\' d=\'m15.987 13.982c0 .906-.413 1.664-1.239 2.274-.757.554-1.604.831-2.541.831-.548 0-.998-.129-1.348-.388-.389-.295-.583-.708-.583-1.238 0-.838.398-1.574 1.192-2.209.752-.597 1.559-.896 2.421-.896.727 0 1.257.145 1.59.434v-9.489l-6.755 1.82v10.774c0 .906-.413 1.663-1.238 2.273-.758.555-1.605.832-2.541.832-.549 0-.998-.13-1.35-.388-.388-.296-.582-.709-.582-1.238 0-.838.398-1.574 1.192-2.209.752-.597 1.559-.896 2.421-.896.727 0 1.257.145 1.589.434v-11.605l7.772-2.098z\'/></svg>',
'plus': '<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m8.001 1h3.999v7h7v4h-7l-.001 7h-3.999v-7h-7v-4h7z\'/></svg>',
'prev_next_track': '<svg viewBox=\'0 0 20 20\'><path fill=\'%23222\' d=\'m13 5h2v10h-2zm-8 0 8 5-8 5z\'/></svg>',
'spinner': '<svg viewBox=\'0 0 100 100\' class=\'display_none invert\' preserveAspectRatio=\'xMidYMid\' width=\'32\' height=\'32\' xmlns=\'http://www.w3.org/2000/svg\'><animateTransform attributeName=\'transform\' type=\'rotate\' values=\'0;45\' keyTimes=\'0;1\' dur=\'0.25s\' repeatCount=\'indefinite\'/><path fill=\'%23000\' fill-opacity=\'.5\' d=\'m29.49-5.5h8v11h-8a30 30 0 0 1 -4.75 11.46l5.66 5.66-7.78 7.78-5.66-5.66a30 30 0 0 1 -11.46 4.75v8h-11v-8a30 30 0 0 1 -11.46-4.75l-5.66 5.66-7.78-7.78 5.66-5.66a30 30 0 0 1 -4.75-11.46h-8v-11h8a30 30 0 0 1 4.75-11.46l-5.66-5.66 7.78-7.78 5.66 5.66a30 30 0 0 1 11.46-4.75v-8h11v8a30 30 0 0 1 11.46 4.75l5.66-5.66 7.78 7.78-5.66 5.66a30 30 0 0 1 4.75 11.46m-29.49-14.5a20 20 0 1 0 0 40 20 20 0 1 0 0-40\' transform=\'matrix(.7189408 .69507131 -.69507131 .7189408 50 50)\'/></svg>',
'toggle': '<svg viewBox=\'0 0 20 20\'><g fill=\'%23222\'><path d=\'m10.207 9.293-.707.707 5.657 5.657 1.414-1.414-4.242-4.243 4.242-4.243-1.414-1.414z\'/><path d=\'m4.207 9.293-.707.707 5.657 5.657 1.414-1.414-4.242-4.243 4.242-4.243-1.414-1.414z\'/></g></svg>',
};
const SVG_UI_File_Icons = { // n.b.: order is important
'file_icon_dir': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6 2.5-1-1.5h-5v12h14v-10.5z\' fill=\'%2339f\'/><path d=\'m1.5 4h11v7.5h-11z\' fill=\'%239cf\'/></svg>',
'file_icon_dir_open': '<svg viewBox=\'0 0 14 14\' clip-rule=\'evenodd\' fill-rule=\'evenodd\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6.1 2.7-1.3-1.7h-4.8v12h14v-10.3z\' fill=\'%2339f\' fill-rule=\'nonzero\'/><path d=\'m7 6h5.5v5.5h-11z\' fill=\'%239cf\'/></svg>',
'file_icon_file': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m8.3 0h-6.8v14h11v-9.8l-4.2-4.2z\' fill=\'%23888\'/><g fill=\'%23fff\'><path d=\'m11 12.5h-8v-11h3.8v4.2h4.2z\'/><path d=\'m8.3 4.2h1.9l-1.9-2z\'/></g></svg>',
'file_icon_invisible': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m8.3 0h-6.8v14h11v-9.8l-4.2-4.2z\' fill=\'%23888\'/><path d=\'m11 12.5h-8v-11h3.8v4.2h4.2z\' fill=\'%23bbb\'/><path d=\'m8.3 4.2h1.9l-1.9-2z\' fill=\'%23bbb\'/><circle cx=\'7\' cy=\'9\' fill=\'%23878787\' r=\'1.5\'/></svg>',
'file_icon_ignored': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'M 10.695,1.774 1.856,10.613 3.482,12.239 12.321,3.4 Z M 7,2 c 2.8,0 5,2.2 5,5 0,2.8 -2.2,5 -5,5 C 4.2,12 2,9.8 2,7 2,4.2 4.2,2 7,2 M 7,0 C 3.1,0 0,3.1 0,7 c 0,3.9 3.1,7 7,7 3.9,0 7,-3.1 7,-7 C 14,3.1 10.9,0 7,0 Z\' style=\'fill:%23888888;fill-opacity:1\' /></svg>',
'file_icon_dirinvisible': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6 2.5-1-1.5h-5v12h14v-10.5z\' fill=\'%23888\'/><path d=\'m1.5 4h11v7.5h-11z\' fill=\'%23bbb\'/><circle cx=\'7\' cy=\'7.5\' fill=\'%23888\' r=\'1.5\'/></svg>',
'file_icon_alias': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%23808080\'/><path d=\'m3 12.5c0-3.863 2.253-7.5 6.259-7.5\' fill=\'none\' stroke=\'%23fc6\' stroke-width=\'3\'/><path d=\'m13 5-4-4v8z\' fill=\'%23fc6\'/></svg>',
'file_icon_archive': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m11 8.5v-1h2v2l-5 1h-2v1.5h4v1h-4v1h-3v-1h-2v-1h2v-1.5h-2v-2h2v-6.5h-2v-2h7l5 1v2h-2v-1h-5v6.5z\' fill=\'%23666\'/></svg>',
'file_icon_app': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6.125 0-.292 1.859c-.587.135-1.146.38-1.64.693v-.018l-1.532-1.094-1.221 1.221 1.094 1.532h.018c-.313.495-.559 1.051-.693 1.64l-1.859.292v1.75l1.859.292c.134.589.38 1.145.693 1.64h-.018l-1.094 1.532 1.221 1.221 1.532-1.094v-.018c.494.313 1.053.558 1.64.693l.292 1.859h1.75l.292-1.859c.596-.137 1.14-.372 1.64-.693l1.532 1.112 1.221-1.221-1.112-1.532c.309-.492.523-1.057.656-1.64l1.896-.292v-1.75l-1.896-.292c-.133-.583-.347-1.148-.656-1.64h.018l1.094-1.532-1.221-1.221-1.532 1.094v.018c-.5-.321-1.044-.556-1.64-.693l-.292-1.859h-1.75zm.875 4.667c1.288 0 2.333 1.036 2.333 2.333s-1.045 2.333-2.333 2.333-2.333-1.036-2.333-2.333 1.045-2.333 2.333-2.333z\' fill=\'%237a7ab8\'/></svg>',
'file_icon_audio': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><circle cx=\'7\' cy=\'7\' fill=\'%230f8a8a\' r=\'7\'/><g fill=\'%23fff\'><path d=\'m11 9.5c-.019.681-.796 1.339-1.75 1.475-.966.138-1.75-.31-1.75-1s.784-1.362 1.75-1.5c.268-.038.523-.031.75.013v-4.488h-4v6.5c-.019.681-.796 1.339-1.75 1.475-.966.138-1.75-.31-1.75-1s.784-1.362 1.75-1.5c.268-.038.523-.031.75.013v-6.488l6-1z\'/><path d=\'m11 2-6 1v2l6-1z\'/></g></svg>',
'file_icon_code': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 0h-14v14h14z\' fill=\'%2372d\'/><g fill=\'%23fff\'><path d=\'m5.923 12.965c-1.049 0-1.784-.161-2.209-.48-.425-.317-.638-.82-.638-1.503v-2.067c0-.446-.146-.764-.438-.95-.292-.188-.709-.281-1.256-.281v-1.368c.547 0 .967-.094 1.259-.28s.438-.5.438-.938v-2.092c0-.675.217-1.172.65-1.491.432-.32 1.164-.479 2.195-.479v1.312c-.401.01-.718.09-.952.24-.233.15-.348.426-.348.827v1.985c0 .876-.511 1.396-1.532 1.559v.083c1.021.154 1.532.67 1.532 1.544v1.997c0 .41.116.688.349.835.233.146.55.223.951.232z\'/><path d=\'m8.076 12.965v-1.313c.392-.009.706-.089.944-.239.236-.15.355-.426.355-.829v-1.996c0-.867.511-1.382 1.531-1.545v-.084c-1.02-.164-1.53-.679-1.53-1.546v-1.997c0-.41-.116-.688-.349-.834-.232-.146-.549-.224-.951-.233v-1.313c1.049 0 1.785.159 2.21.479.423.319.637.821.637 1.505v2.065c0 .447.146.765.438.951.292.187.711.28 1.257.28v1.367c-.546.012-.967.107-1.259.287-.293.183-.438.5-.438.945v2.08c0 .674-.217 1.172-.65 1.491-.432.319-1.165.479-2.195.479z\'/></g></svg>',
'file_icon_database': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 2.5v9c0 1.38-3.137 2.5-7 2.5s-7-1.12-7-2.5v-9\' fill=\'%23808080\'/><path d=\'m13 2.5v9c0 .828-2.689 1.5-6 1.5s-6-.672-6-1.5v-9\' fill=\'%23b4b4b4\'/><path d=\'m14 8.5c0 1.38-3.137 2.5-7 2.5s-7-1.12-7-2.5\' fill=\'%23808080\'/><path d=\'m13 8.5c0 .828-2.689 1.5-6 1.5s-6-.672-6-1.5\' fill=\'%23b4b4b4\'/><path d=\'m14 5.5c0 1.38-3.137 2.5-7 2.5s-7-1.12-7-2.5\' fill=\'%23808080\'/><path d=\'m13 5.5c0 .828-2.689 1.5-6 1.5s-6-.672-6-1.5\' fill=\'%23b4b4b4\'/><ellipse cx=\'7\' cy=\'2.5\' fill=\'%23808080\' rx=\'7\' ry=\'2.5\'/><ellipse cx=\'7\' cy=\'2.5\' fill=\'%23b4b4b4\' rx=\'5.5\' ry=\'1.5\'/></svg>',
'file_icon_ebook': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m2.668-.001c1.705.001 3.492.35 4.332 1.257.84-.908 2.627-1.256 4.332-1.257h2.668v12.541c-.818 0-2.181.005-3 .023-1.184.026-3.008.42-3 1.437l-1-.017-1 .017c.008-1.017-2-1.437-3-1.437-.819 0-2.182-.023-3-.023v-12.541h2.668z\' fill=\'%23808080\'/><g fill=\'%23cdcdcd\'><path d=\'m1.5 1.499v9.501h1.286c1.086.025 2.213.081 3.204.568l.01.006c0-2.859 0-5.717 0-8.576 0-1.136-1.49-1.398-2.336-1.47-.708-.059-1.438-.029-2.164-.029z\'/><path d=\'m12.5 1.499v9.501h-1.286c-1.086.025-2.213.081-3.204.568l-.01.006c0-2.859 0-5.717 0-8.576 0-1.136 1.49-1.398 2.336-1.47.708-.059 1.438-.029 2.164-.029z\'/></g></svg>',
'file_icon_font': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 0h-14v14h14z\' fill=\'%23709\'/><path d=\'m4.678 11.179h1.393v-8.266h-2.616v1.052h-1.455v-2.553h10v2.554h-1.456v-1.053h-2.599v8.266h1.347v1.409h-4.614z\' fill=\'%23fff\'/></svg>',
'file_icon_graphics': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%23808080\'/><path d=\'m7.774 8.285 4.726 4.715-8-3.525-1.5-4.975h-2v-3.5h3.525l-.025 2 5 1.5 3.5 8-4.7-4.752c.127-.22.2-.476.2-.748 0-.828-.672-1.5-1.5-1.5s-1.5.672-1.5 1.5.672 1.5 1.5 1.5c.283 0 .548-.079.774-.215z\' fill=\'%23ccc\'/></svg>',
'file_icon_htm': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m6.967.5c-3.553.018-6.467 2.947-6.467 6.5 0 3.566 2.934 6.5 6.5 6.5s6.5-2.934 6.5-6.5c0-3.553-2.914-6.482-6.467-6.5zm.033 0v13m6.5-6.5h-13m1.467-4c3.004 2.143 7.062 2.143 10.066 0m0 8c-3.004-2.143-7.062-2.143-10.066 0m4.533-10.333c-1.874 1.582-2.957 3.914-2.957 6.366 0 2.453 1.083 4.785 2.957 6.367m1 0c1.874-1.582 2.957-3.914 2.957-6.367 0-2.452-1.083-4.784-2.957-6.366\' fill=\'%23fff\' stroke=\'%23e44d26\'/></svg>',
'file_icon_ignoredimage': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m.369 9.141c-.252-.678-.369-1.396-.369-2.141 0-3.863 3.137-7 7-7s7 3.137 7 7l-.137 1.353-3.853-3.853-3.5 3.5-2.5-2.5z\' fill=\'%23808080\'/><path d=\'m.839 10.151-.47-1.01 3.641-3.641 2.5 2.5 3.5-3.5 3.853 3.853c-.076.395-.201.778-.341 1.147l-10.371 3.345c-.293-.194-.579-.416-.838-.651z\' fill=\'%23fff\'/><path d=\'m13.522 9.5c-.99 2.64-3.539 4.5-6.522 4.5-1.426 0-2.753-.421-3.849-1.155l6.859-6.866z\' fill=\'%23808080\'/><path d=\'m.839 10.151 3.171-3.172 1.761 1.761-3.459 3.454c-.591-.632-1.079-1.313-1.473-2.043z\' fill=\'%23808080\'/><circle cx=\'6\' cy=\'3.5\' fill=\'%23fff\' r=\'1.5\'/></svg>',
'file_icon_image': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m.369 9.141c-.252-.678-.369-1.396-.369-2.141 0-3.863 3.137-7 7-7s7 3.137 7 7l-.137 1.353-3.853-3.853-3.5 3.5-2.5-2.5z\' fill=\'%238080ff\'/><path d=\'m.839 10.151-.47-1.01 3.641-3.641 2.5 2.5 3.5-3.5 3.853 3.853c-.076.395-.201.778-.341 1.147l-10.371 3.345c-.293-.194-.579-.416-.838-.651z\' fill=\'%23fff\'/><path d=\'m13.522 9.5c-.99 2.64-3.539 4.5-6.522 4.5-1.426 0-2.753-.421-3.849-1.155l6.859-6.866z\' fill=\'%2333c\'/><path d=\'m.839 10.151 3.171-3.172 1.761 1.761-3.459 3.454c-.591-.632-1.079-1.313-1.473-2.043z\' fill=\'%2333c\'/><circle cx=\'6\' cy=\'3.5\' fill=\'%23fff\' r=\'1.5\'/></svg>',
'file_icon_markdown': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 0h-14v14h14z\' fill=\'%236a6a95\'/><path d=\'m12 11.5h-2.5v-5.143l-2.5 2.948-2.5-2.948v5.143h-2.5v-9h2.273l2.721 3.377 2.733-3.377h2.273z\' fill=\'%23ddd\'/></svg>',
'file_icon_office': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%23808080\'/><g fill=\'%23cdcdcd\'><path d=\'m10 1.5h2.5v1h-2.5z\'/><path d=\'m10 4h2.5v1h-2.5z\'/><path d=\'m10 6.5h2.5v1h-2.5z\'/><path d=\'m10 9h2.5v1h-2.5z\'/><path d=\'m10 11.5h2.5v1h-2.5z\'/><path d=\'m6.5 1.5h2.5v1h-2.5z\'/><path d=\'m6.5 4h2.5v1h-2.5z\'/><path d=\'m6.5 6.5h2.5v1h-2.5z\'/><path d=\'m6.5 9h2.5v1h-2.5z\'/><path d=\'m6.5 11.5h2.5v1h-2.5z\'/><path d=\'m1.5 1.5h4v11h-4z\'/></g></svg>',
'file_icon_pdf': '<svg clip-rule=\'evenodd\' fill-rule=\'evenodd\' viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h13.999986v13.999986h-13.999986z\' fill=\'%23e0382d\' stroke-width=\'.259259\'/><path d=\'m45 31.2c-2.6-2.7-9.7-1.6-11.4-1.4-2.5-2.4-4.2-5.3-4.8-6.3.9-2.7 1.5-5.4 1.6-8.3 0-2.5-1-5.2-3.8-5.2-1 0-1.9.6-2.4 1.4-1.2 2.1-.7 6.3 1.2 10.6-1.1 3.1-2.1 6.1-4.9 11.4-2.9 1.2-9 4-9.5 7-.2.9.1 1.8.8 2.5.7.6 1.6.9 2.5.9 3.7 0 7.3-5.1 9.8-9.4 2.1-.7 5.4-1.7 8.7-2.3 3.9 3.4 7.3 3.9 9.1 3.9 2.4 0 3.3-1 3.6-1.9.5-1 .2-2.1-.5-2.9zm-2.5 1.7c-.1.7-1 1.4-2.6 1-1.9-.5-3.6-1.4-5.1-2.6 1.3-.2 4.2-.5 6.3-.1.8.2 1.6.7 1.4 1.7zm-16.7-20.6c.2-.3.5-.5.8-.5.9 0 1.1 1.1 1.1 2-.1 2.1-.5 4.2-1.2 6.2-1.5-4-1.2-6.8-.7-7.7zm-.2 19.4c.8-1.6 1.9-4.4 2.3-5.6.9 1.5 2.4 3.3 3.2 4.1 0 .1-3.1.7-5.5 1.5zm-5.9 4c-2.3 3.8-4.7 6.2-6 6.2-.2 0-.4-.1-.6-.2-.3-.2-.4-.5-.3-.9.3-1.4 2.9-3.3 6.9-5.1z\' fill=\'%23fff\' fill-rule=\'nonzero\' transform=\'matrix(.344737 0 0 .35503 -2.77114 -2.5503)\'/></svg>',
'file_icon_playlist': '<svg viewBox=\'0 0 14 14\' clip-rule=\'evenodd\' fill-rule=\'evenodd\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%230f8a8a\' fill-rule=\'nonzero\'/><path d=\'m1.5 1.5h8v1h-8zm0 2.5h8v1h-8zm0 2.5h8v1h-8zm0 2.5h7v1h-7zm0 2.5h5.5v1h-5.5zm9.5-10h1v10c-.019.681-.796 1.339-1.75 1.475-.966.138-1.75-.31-1.75-1s.784-1.362 1.75-1.5a2.28 2.28 0 0 1 .75.013z\' fill=\'%23fff\'/></svg>',
'file_icon_text': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 0h-14v14h14z\' fill=\'%236a6a95\'/><g fill=\'%23fff\'><path d=\'m6.5 1.5h6v1h-6z\'/><path d=\'m1.5 1.5h3.5v3.5h-3.5z\'/><path d=\'m1.5 6.5h11v1h-11z\'/><path d=\'m6.5 4h6v1h-6z\'/><path d=\'m1.5 11.5h8v1h-8z\'/><path d=\'m1.5 9h11v1h-11z\'/></g></svg>',
'file_icon_video': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m14 14v-14h-14v14z\'/><g fill=\'%23fff\'><path d=\'m9.5 3v-2h-2v2z\'/><path d=\'m3.5 3v-2h-2v2z\'/><path d=\'m6.5 3v-2h-2v2z\'/><path d=\'m12.5 3v-2h-2v2z\'/><path d=\'m9.5 13v-2h-2v2z\'/><path d=\'m3.5 13v-2h-2v2z\'/><path d=\'m6.5 13v-2h-2v2z\'/><path d=\'m12.5 13v-2h-2v2z\'/></g><path d=\'m12.5 10v-6h-11v6z\' fill=\'%23eda412\'/></svg>',
'file_icon_bin': '', 'file_icon_other': '', // <-- these two use file_icon_system:
'file_icon_system': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m0 0h14v14h-14z\' fill=\'%23808080\'/><g fill=\'%23ccc\'><path d=\'m1.247 6.495h3.263v-1.067h-.881v-3.835h-.974c-.371.232-.727.371-1.284.479v.82h.928v2.536h-1.052z\'/><path d=\'m7 6.588c1.082 0 1.825-.89 1.825-2.567 0-1.67-.743-2.521-1.825-2.521s-1.825.843-1.825 2.521c0 1.677.743 2.567 1.825 2.567zm0-1.021c-.309 0-.572-.247-.572-1.546s.263-1.5.572-1.5.572.201.572 1.5-.263 1.546-.572 1.546z\'/><path d=\'m9.598 6.495h3.263v-1.067h-.882v-3.835h-.974c-.371.232-.727.371-1.283.479v.82h.927v2.536h-1.051z\'/><path d=\'m2.825 12.588c1.082 0 1.824-.89 1.824-2.567 0-1.67-.742-2.521-1.824-2.521-1.083 0-1.825.843-1.825 2.521 0 1.677.742 2.567 1.825 2.567zm0-1.021c-.31 0-.572-.247-.572-1.546s.262-1.5.572-1.5c.309 0 .572.201.572 1.5s-.263 1.546-.572 1.546z\'/><path d=\'m5.423 12.495h3.263v-1.067h-.882v-3.835h-.974c-.371.232-.727.371-1.284.479v.82h.928v2.536h-1.051z\'/><path d=\'m11.175 12.588c1.083 0 1.825-.89 1.825-2.567 0-1.67-.742-2.521-1.825-2.521-1.082 0-1.824.843-1.824 2.521 0 1.677.742 2.567 1.824 2.567zm0-1.021c-.309 0-.572-.247-.572-1.546s.263-1.5.572-1.5c.31 0 .572.201.572 1.5s-.262 1.546-.572 1.546z\'/></g></svg>'
};
const SVG_Text_Editing_UI_Icons = {
'toggle_theme': '<svg viewBox=\'0 0 16 16\' clip-rule=\'evenodd\' fill-rule=\'evenodd\' xmlns=\'http://www.w3.org/2000/svg\'><path d=\'m8 0c4.415 0 8 3.585 8 8s-3.585 8-8 8-8-3.585-8-8 3.585-8 8-8zm0 2c3.311 0 6 2.689 6 6s-2.689 6-6 6z\' fill=\'%23333\'/></svg>',
'show_markdown': '<svg viewBox=\'0 0 100 60\' xmlns=\'http://www.w3.org/2000/svg\' width=\'100\'><g fill=\'%23333\'><path d=\'M42.215 60l.17-46.24h-.255L30.06 60h-7.99L10.255 13.76H10L10.169 60H.905V-.18H14.59l11.56 44.03h.34L37.794-.18H52.16V60h-9.945zM99.589 29.996c0 9.519-1.997 16.901-5.992 22.142C89.602 57.38 83.722 60 75.959 60H60.914V-.18h15.13c7.706 0 13.558 2.65 17.553 7.948 3.995 5.299 5.992 12.708 5.992 22.228zm-10.2 0c0-3.57-.326-6.686-.978-9.35-.651-2.663-1.572-4.873-2.762-6.63-1.19-1.756-2.607-3.073-4.25-3.953-1.645-.878-3.43-1.317-5.355-1.317h-4.845v42.33h4.845c1.926 0 3.711-.438 5.355-1.317 1.643-.878 3.06-2.195 4.25-3.953 1.189-1.756 2.11-3.952 2.762-6.587.651-2.637.978-5.709.978-9.223z\'/></g></svg>',
'show_source': '<svg viewBox=\'0 0 22 14\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><path fill=\'none\' d=\'M0 0h21.996v14H0z\'/><clipPath id=\'a\'><path d=\'M0 0h21.996v14H0z\'/></clipPath><g clip-path=\'url(%23a)\' fill=\'%23333\'><path d=\'M0 7.393v-.786l6.062-3.5.75 1.3L2.32 7l4.492 2.593-.75 1.3L0 7.393zM21.996 6.607v.786l-6.062 3.5-.75-1.3L19.676 7l-4.492-2.593.75-1.3 6.062 3.5zM15.15 1.313l-1.3-.75-7 12.124 1.3.75 7-12.124z\'/></g></svg>',
'show_preview': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><path d=\'M10 2.5V1H0v1.5h4V15h2V2.5h4zM9 6.5V8h2v4.053c0 2.211 1.547 3.442 3 3.442.989 0 1.556-.258 2-.495v-1.5c-.565.257-.882.376-1.507.376-.847 0-1.493-.474-1.493-1.876V8h2.5V6.5H13v-3h-1.98v3H9z\' fill=\'%23333\' fill-rule=\'nonzero\'/></svg>',
'show_html': '<svg viewBox=\'0 0 22 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><path fill=\'none\' d=\'M0 0h21.022v16H0z\'/><clipPath id=\'a\'><path d=\'M0 0h21.022v16H0z\'/></clipPath><g clip-path=\'url(%23a)\' fill=\'%23333\'><path d=\'M7.732.222L9.5 1.99 3.49 8l6.01 6.01-1.768 1.768L-.046 8 7.732.222zM13.268 15.778L11.5 14.01 17.51 8 11.5 1.99 13.268.222 21.046 8l-7.778 7.778z\'/></g></svg>',
'toggle_split': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><path d=\'M0 0v16h16V0H0zm14 14H9V2h5v12zm-7 0H2V2h5v12z\' fill=\'%23333\' fill-rule=\'nonzero\'/></svg>',
'save_btn': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><g fill=\'currentColor\'><path d=\'M16 0v10.02L14 10V2H2v8l-2 .02V0h16z\' fill-rule=\'nonzero\'/><path d=\'M7 5h2v9H7z\'/><path d=\'M3.757 11.757l1.415-1.414L8 13.172l2.828-2.829 1.415 1.414L8 16l-4.243-4.243z\'/></g></svg>',
'save_btn_edited': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\'><g fill=\'%23DD2222\'><path d=\'M16 0v10.02L14 10V2H2v8l-2 .02V0h16z\' fill-rule=\'nonzero\'/><path d=\'M7 5h2v9H7z\'/><path d=\'M3.757 11.757l1.415-1.414L8 13.172l2.828-2.829 1.415 1.414L8 16l-4.243-4.243z\'/></g></svg>'
};
function get_SVG_UI_Icon(icon_name) { return `url("data:image/svg+xml;utf8,${ SVG_UI_Icons[icon_name] }")`; } // ===> GET SVG UI ICON by name
function get_SVG_UI_File_Icon(icon_name) { // ===> GET SVG UI FILE Icon by name
switch(icon_name) {
case 'favicon': return 'iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUmRcmZzP8zmf8pVcWPAAAAAXRSTlMAQObYZgAAAFBJREFUeF7tyqERwDAMBEE3mX5UiqDmqwwziTPHjG7xrmzrLFtRaApDIRiKQlMYCsFQFJrCUAiGotAU5hTA1WB4fhkMBsOJwWAwgHvB8CHpBcTbpxy4RZNvAAAAAElFTkSuQmCC';
case 'file_icon_dir_default': return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAd5JREFUeNqMU79rFUEQ/vbuodFEEkzAImBpkUabFP4ldpaJhZXYm/RiZWsv/hkWFglBUyTIgyAIIfgIRjHv3r39MePM7N3LcbxAFvZ2b2bn22/mm3XMjF+HL3YW7q28YSIw8mBKoBihhhgCsoORot9d3/ywg3YowMXwNde/PzGnk2vn6PitrT+/PGeNaecg4+qNY3D43vy16A5wDDd4Aqg/ngmrjl/GoN0U5V1QquHQG3q+TPDVhVwyBffcmQGJmSVfyZk7R3SngI4JKfwDJ2+05zIg8gbiereTZRHhJ5KCMOwDFLjhoBTn2g0ghagfKeIYJDPFyibJVBtTREwq60SpYvh5++PpwatHsxSm9QRLSQpEVSd7/TYJUb49TX7gztpjjEffnoVw66+Ytovs14Yp7HaKmUXeX9rKUoMoLNW3srqI5fWn8JejrVkK0QcrkFLOgS39yoKUQe292WJ1guUHG8K2o8K00oO1BTvXoW4yasclUTgZYJY9aFNfAThX5CZRmczAV52oAPoupHhWRIUUAOoyUIlYVaAa/VbLbyiZUiyFbjQFNwiZQSGl4IDy9sO5Wrty0QLKhdZPxmgGcDo8ejn+c/6eiK9poz15Kw7Dr/vN/z6W7q++091/AQYA5mZ8GYJ9K0AAAAAASUVORK5CYII= ")';
case 'file_icon_file_default': return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
default: return 'url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons[icon_name] +'")';
}
}
function CSS_UI_Icon_Rules() { // programatically add File icon CSS rules // ===> CSS UI ICON RULES
let rules = '', kind, class_name;
rules = `
#menu ul a::before { background-image:${ get_SVG_UI_File_Icon('file_icon_file') }; }
#menu ul a[href^="file"]::before { background-image:${ get_SVG_UI_File_Icon('file_icon_dir') }; }
#menu ul a[href^="http"]::before { background-image:${ get_SVG_UI_File_Icon('file_icon_htm') }; }
body:not(.use_custom_icons) .dir .has_icon_before_before { background-image:${ get_SVG_UI_File_Icon('file_icon_dir_default') }; background-size:auto 13px; }
body:not(.use_custom_icons) .file:not(.app) .has_icon_before_before { background-image:${ get_SVG_UI_File_Icon('file_icon_file_default') }; background-size:auto 13px; }
`;
for ( let icon in SVG_UI_File_Icons ) {
kind = icon.slice(icon.lastIndexOf('_') + 1);
class_name = kind;
// exceptions
if ( kind === 'dirinvisible' ) { class_name = 'dir.invisible'; }
if ( kind === 'ignoredimage' ) { class_name = 'ignored_image'; }
if ( kind === 'open' ) { class_name = 'has_subdirectory'; kind = 'dir_open'; }
if ( /alias|symlink/.test(kind) ) { class_name = 'link'; }
if ( /bin|other/.test(kind) ) { kind = 'system'; }
// add rules for dir_list items, content_header, stats details:
rules += `.use_custom_icons .${ class_name } a.icon .has_icon_before_before, #content_pane[data-content="has_${ class_name }"] #content_title span::before, .use_custom_icons .${ class_name }.has_icon_before::before, .use_custom_icons .${ class_name } .has_icon_before::before { background-image: url("data:image/svg+xml;utf8,${ SVG_UI_File_Icons['file_icon_'+kind] }"); }`;
}
return rules;
}
// END SVG UI ICONS
//==============================// UI HTML
// SIDEBAR HTML
const Directory_Header_Menu_Elements = `
<li id="menu_sort_by" class="menu__menu-item has_submenu border_top border_bottom" title="Sorting options."><span class="menu_item">Sort by…</span>${SVG_UI_Icons['arrow']}
<ul id="sort_menu" class="submenu background_grey_85 border_all"> <li id="menu_sort_by_name" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_name"><span class="menu_item">Name</span></li> <li id="menu_sort_by_duration" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_duration"><span class="menu_item">Duration</span></li> <li id="menu_sort_by_size" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_size"><span class="menu_item">Size</span></li> <li id="menu_sort_by_date" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_date"><span class="menu_item">Date</span></li> <li id="menu_sort_by_kind" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_kind"><span class="menu_item">Kind</span></li> <li id="menu_sort_by_ext" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_ext"><span class="menu_item">Extension</span></li> <li id="menu_sort_by_default" class="is_submenu toggle_UI_pref sorting" data-ui_pref="sort_by_default"><span class="menu_item">Default</span></li> </ul></li>
<li id="menu_theme_container" title="Set the main UI theme (light or dark)."><span id="menu_theme" class="toggle_UI_pref menu_item ignore_warning" data-ui_pref="theme"><span> Theme</span></span></li>
<li id="alternate_background" class="menu__menu-item toggle_UI_pref ignore_warning" data-ui_pref="alternate_background" title="Alternate backgrounds of directory items."><span class="menu_item">Alternate Backgrounds</span></li>
<li id="show_image_thumbnails" class="menu__menu-item toggle_UI_pref ignore_warning" data-ui_pref="show_image_thumbnails" title="Show image thumbnails in sidebar."><span class="menu_item">Show Image Thumbnails</span></li>
<li id="show_numbers" class="menu__menu-item toggle_UI_pref border_bottom ignore_warning" data-ui_pref="show_numbers" title="Number directory list items."><span class="menu_item">Show Numbers</span></li>
<li id="ignored_files" class="menu__menu-item has_submenu border_bottom" title="Options for ignored items."><span class="menu_item">Ignored Items</span>${SVG_UI_Icons['arrow']} <ul id="ignored_files_submenu" class="submenu background_grey_85 border_all"> <li id="hide_ignored_items" class="is_submenu toggle_UI_pref border_bottom" data-ui_pref="hide_ignored_items" title="Show/hide ignored items (from the list of ignored file types in the user settings)."><span class="menu_item">Hide Ignored Items</span></li> <li id="ignore_ignored_items" class="is_submenu toggle_UI_pref " data-ui_pref="ignore_ignored_items" title="If checked, the browser will not attempt to load ignored items (from the list of ignored file types in the user settings). It is recommended to leave this checked."><span class="menu_item">Ignore Ignored Items</span></li> </ul></li>
<li id="autoload_files" class="menu__menu-item has_submenu border_bottom" title="Options for autoloading files."><span class="menu_item">Autoload Files</span>${SVG_UI_Icons['arrow']} <ul id="autoload_files_menu" class="submenu background_grey_85 border_all"> <li id="autoload_media" class="is_submenu toggle_UI_pref border_bottom" data-ui_pref="autoload_media" title="Automatically select and load the first media item in a directory and cover art (if any)."><span id="autoload_media_menu" class="menu_item">Autoload Media</span></li> <li id="autoload_index_files" class="is_submenu toggle_UI_pref" data-ui_pref="autoload_index_files" title="Automatically load html index file."><span id="autoload_index_files_menu" class="menu_item">Autoload Index Files</span></li></ul>
<li id="media_files" class="menu__menu-item has_submenu border_bottom" title="Options for media files."><span class="menu_item">Media Files</span>${SVG_UI_Icons['arrow']} <ul id="media_files_menu" class="submenu background_grey_85 border_all"> <li id="play_all_media" class="is_submenu toggle_UI_pref border_bottom" data-ui_pref="play_all_media" title="If checked, autoplay all media types (i.e., audio and video), else just autoplay the currently selected/playing media type."><span class="menu_item">Play All Media Files</span></li> <li id="loop_media_files" class="is_submenu" title="If checked, loop media playback."><span id="loop_media_menu" class="menu_item">Loop Media Playback</span></li> <li id="shuffle_media_files" class="is_submenu" title="If checked, shuffle media playback."><span id="shuffle_media_menu" class="menu_item">Shuffle Media Playback</span></li></ul>
<li id="playlist_options" class="menu__menu-item has_submenu border_bottom" title="Options for playlists."><span class="menu_item">Playlists</span>${SVG_UI_Icons['arrow']} <ul id="playlist_menu" class="submenu background_grey_85 border_all"> <li class="is_submenu border_bottom"><label id="open_playlist_label" class="menu_item" for="open_playlist" title="Open local .m3u playlist/filelist file.">Open Playlist/Filelist…</label><input type="file" id="open_playlist" name="open_playlist" accept=".m3u,.m3u8"></li> <li id="close_playlist_container" class="is_submenu"><span id="close_playlist" class="menu_item" href="#">Close Playlist/Filelist</span></li> <li class="is_submenu"><span id="make_playlist" class="menu_item" href="#" title="Make an .m3u playlist/filelist of the items in the current directory (if any).">Make Playlist/Filelist…</span></li> </ul></li>
<li id="toggle_text_editing" class="menu__menu-item" title="Enable/disable editing of plain text files. Does not effect main text editor."><span id="enable_text_editing" class="toggle_UI_pref menu_item" data-ui_pref="enable_text_editing"><span id="disable">Text Editing </span></span></li>
<li id="text_editing" class="menu__menu-item has_submenu border_bottom" title="Text editing options."><span class="menu_item">Text Editing Options</span>${SVG_UI_Icons['arrow']} <ul id="text_editing_menu" class="submenu background_grey_85 border_all"> <li id="text_editor_menu_item" class="is_submenu border_bottom" title="Toggle the main text editor."><span id="text_editor" class="menu_item">Toggle Text Editor </span></li> <li id="toggle_text_editor_theme_default" class="is_submenu border_bottom" title="Editor theme same as the main UI theme."><span id="text_editor_theme_default" class="toggle_UI_pref menu_item" data-ui_pref="text_editor_theme_default">Default Text Editor Theme</span></li> <li id="toggle_text_editor_theme_light" class="is_submenu" title="Light text editor theme."><span id="text_editor_theme_light" class="toggle_UI_pref menu_item" data-ui_pref="text_editor_theme_light" title="Light text editor theme.">Light Text Editor Theme</span></li> <li id="toggle_text_editor_theme_dark" class="is_submenu border_bottom" title="Dark text editor theme."><span id="text_editor_theme_dark" class="toggle_UI_pref menu_item" data-ui_pref="text_editor_theme_dark" title="Dark text editor theme.">Dark Text Editor Theme</span></li> <li id="text_editor_split_view" class="is_submenu toggle_UI_pref border_bottom" data-ui_pref="text_editor_split_view" title="Toggle display of default text view and both source and rendered text."><span class="menu_item">Split View</span></li> <li id="toggle_text_editor_raw_menu" class="is_submenu toggle_UI_pref menu_item" data-ui_pref="text_editor_raw">View Source Markdown/Text</li> <li id="toggle_text_editor_preview_menu" class="is_submenu toggle_UI_pref menu_item" data-ui_pref="text_editor_preview">View Styled Markdown/Text</li> <li id="toggle_text_editor_html_menu" class="is_submenu toggle_UI_pref menu_item" data-ui_pref="text_editor_html">View Rendered HTML</li></ul>
<li id="open_font_file" class="menu__menu-item border_bottom"><label id="open_font_label" class="menu_item" for="open_font" title="Open font file (.oft, .ttf, .woff) to view glyph repertoire and font info; save individual glyphs as .svg.">Open Font File…</label><input type="file" id="open_font" name="open_font" accept=".otf,.ttf,.woff"></li>
<li id="user_settings" class="menu__menu-item has_submenu border_bottom" title="Reset or save user settings."><span class="menu_item">User Settings</span>${SVG_UI_Icons['arrow']} <ul id="user_settings_menu" class="submenu background_grey_85 border_all"> <li class="is_submenu"><span id="default_settings" class="menu_item border_bottom" href="#" title="Delete UI prefs stored in the URL query string and reload page.">Reset User Settings</span></li> <li class="is_submenu"><span id="export_settings" class="menu_item" href="#" title="Export hard-coded user settings and bookmarks to text file.">Export User Settings</span></li> </ul></li>
<li id="about" class="menu__menu-item" title="Go to script home page."><a id="about_link" class="menu_item flex_grow_1 ignore_warning" href="https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser" target="_blank"><span class="icon_container"></span>About</a></li>
<li id="show_help" class="menu__menu-item " title="Show help."><span class="menu_item flex_grow_1"><span class="icon_container"></span>Help</span></li>
<li id="donate" class="menu__menu-item " title="Buy me a coffee!"><a id="donate_link" class="menu_item flex_grow_1 ignore_warning" href="https://www.buymeacoffee.com/fiLtliTFxQ" target="_blank" rel="noopener"><span class="icon_container"></span>Buy me a Coffee <svg xmlns="http://www.w3.org/2000/svg" width="14px" viewBox="0 0 64 64" enable-background="new 0 0 64 64"><path d="m32 2c-16.568 0-30 13.432-30 30 0 16.568 13.432 30 30 30s30-13.432 30-30c0-16.568-13.432-30-30-30m0 48c-1.371-1.814-20.53-12.883-16.602-25.218 3.53-11.073 15.094-6.597 16.602-.594 1.094-5.635 12.949-10.694 16.604.584 3.925 12.136-15.237 23.785-16.604 25.228" fill="#757575"/></svg></a></li>
<li id="contact" class="menu__menu-item " title="Send me an email."><a id="contact_link" class="menu_item flex_grow_1 ignore_warning" href="mailto:mshroud@protonmail.com"><span class="icon_container"></span>Contact</a></li>
`;
const Directory_Header_Elements = function(body_id,parent_link) {
let checked = '', parent_links = createParentLinkItems(), directory_title_element = '', directory_header_menus = '', text_editor_element = '';
if ( getSearchParam('show_invisibles') === 'true' ) { checked = 'checked="true"'; }
switch(body_id) {
case 'top':
directory_title_element = `<div id="directory_title" class="display_flex_row border_bottom background_grey_75 normal"><div class="directory_title_div center padding_4_6"></div></div>`;
directory_header_menus = `<div id="directory_menus" class="display_flex_row background_grey_75 border_bottom pointer">
<div id="parent_dir_menu" class="flex_justify_center width_24px padding_0"> <nav id="parent_dir_nav" class="flex_justify_center invert"><a href="${ parent_links[1] }" title="Parent Directory" class="flex_justify_center"><div>${ SVG_UI_Icons['chevron'] }</div></a></nav> </div>
<div id="parents_dir_menu" class="padding_0 flex_grow_1"> <nav id="parents_dir_nav" class="display_flex border_right border_left"> <div id="current_dir_path" class="bold flex_justify_center pointer" title="Parent Directories"><span class="has_icon_before">${ current_dir_path }</span></div> <div id="close_playlist_btn_container" class="flex_justify_center width_24px border_left padding_0" title="Close Playlist"> <div id="close_playlist_btn" class="display_flex_column width_24px has_background invert padding_0"></div> </div> </nav> <ul id="parents_links" class="menu background_grey_85 position_absolute border_bottom margin_0 padding_0 display_none">${ parent_links[0] }</ul> </div>
<div id="menu_container" class="width_24px flex_justify_center margin_0 padding_0" title="Main menu"> <nav id="menu_nav" class="invert pointer width_14px_contents"><div>${ SVG_UI_Icons['menu'] }</div></nav> <ul id="menu" class="menu position_absolute background_grey_85 border_bottom margin_0 padding_0 display_none"> ${ bookmarksMenuItems() } ${ Directory_Header_Menu_Elements } </ul> </div>
</div>`;
text_editor_element = `<div id="show_text_editor" class="bold border_bottom background_grey_80 display_none"><a href="#" title="Toggle Text Editor">Text Editor</a></div>`;
break;
case 'iframe':
directory_header_menus = `<div id="change_dirs" class="flex_justify_center_row flex_justify_contents border_bottom background_grey_75"> <span id="parent" class="hover_bold flex_grow_1"><a href="${ parent_link }" id="iframe_parent_link" class="display_flex" title="Go to parent directory"><span class="width_14px_contents invert">${ SVG_UI_Icons['chevron'] }</span>Parent Directory</a></span> <span id="open_in_sidebar" class="align_right hover_bold flex_grow_1"><a href="#" title="Open this directory in sidebar">Open in Sidebar<span class="width_14px_contents invert transform_rotate_270_contents">${ SVG_UI_Icons['chevron'] }</span></a></span> </div>`;
break;
}
return `<header id="directory_header" class="display_flex_column text_color_default">
${ directory_title_element }
<div id="directory_header_utilities" class="display_flex_column">
${ directory_header_menus }
<div id="directory_buttons" class="display_flex_row position_relative background_grey_80 border_bottom">
<div id="directory_buttons_left" class="display_flex"> <button id="show_details" class="toggle_UI_pref" data-ui_pref="show_details" tabindex="-1" title="Toggle display of directory item detail information"><span id="show"> details</span></button> <label id="show_invisibles_container" for="inv_checkbox" class="margin_0 padding_0 flex_justify_center_row"><input class="toggle_UI_pref" type="checkbox" id="show_invisibles" data-ui_pref="show_invisibles" name="inv_checkbox" tabindex="-1"${ checked } /><span>Show Invisibles</span></label> </div>
<div id="show_grid" class="width_24px pointer margin_0 padding_0 has_flyout_menu" tabindex="-1" title="Show Grid"><div class="display_flex width_14px_contents">${ SVG_UI_Icons['grid']}</div> <ul class="menu has_popout_menu margin_0 padding_0 display_none"><div class="display_flex width_24px width_14px_contents">${ SVG_UI_Icons['grid']}</div> <li id="show_image_grid" class="item_1 border_right border_bottom">Show Image Grid</li> <li id="show_font_grid" class="item_2 border_right">Show Font Grid</li> </ul> </div>
</div>
<div id="sorting" class="background_grey_80">
<div id="sorting_row_1" class="container display_flex flex_justify_contents border_bottom"> <div id="sort_by_name" class="toggle_UI_pref name sorting align_left hover_bold" data-ui_pref="sort_by_name" title="Sort by name"><span><input id="play_toggle" class="display_none position_relative" type="checkbox" tabindex="-1" checked="true" />Name</span></div> <div id="sort_by_default" class="toggle_UI_pref sorting align_right hover_bold" data-ui_pref="sort_by_default" title="Default sort"><span>Default</span></div> <div id="sort_by_duration" class="toggle_UI_pref sorting align_right hover_bold display_none" data-ui_pref="sort_by_duration" title="Sort by media duration"><span>Duration</span></div> </div>
<div id="sorting_row_2" class="iframe_item border_bottom display_none"> <div id="sort_by_ext" class="toggle_UI_pref details sorting align_left hover_bold" data-ui_pref="sort_by_ext" title="Sort by extension"><span>Ext</span></div> <div id="sort_by_size" class="toggle_UI_pref details sorting center hover_bold" data-ui_pref="sort_by_size" title="Sort by size"><span>Size</span></div> <div id="sort_by_date" class="toggle_UI_pref details sorting center hover_bold" data-ui_pref="sort_by_date" title="Sort by date"><span>Date</span></div> <div id="sort_by_kind" class="toggle_UI_pref details sorting align_right hover_bold" data-ui_pref="sort_by_kind" title="Sort by kind"><span>Kind</span></div> </div>
</div>
${ text_editor_element }
</div>
</header>`;
};
const Directory_Elements = function(body_id,parent_link) { // Assemble directory elements for both top and iframe directories
let footer_utilities = '', directory_utilities = '';
if ( body_id === 'top' ) { // various elements not needed in iframe directories
footer_utilities = `<div id="footer_utilities" class="width_24px flex_justify_center position_relative invert pointer"><div class="width_18px_contents display_flex transform_rotate_180">${ SVG_UI_Icons['toggle'] }</div> <ul class="has_popout_menu invert margin_0 padding_0 display_none"> <li id="open_in_content_pane" class="align_right border_bottom padding_4_6">Open Sidebar in Content Pane</li> <li id="view_directory_source" class="align_right padding_4_6" data-kind="view_directory_source">View Sidebar Directory Source</li> </ul> </div>`;
directory_utilities = `<div id="handle" class="position_absolute "></div> <div id="hide_sidebar" class="toggle_UI_pref width_24px width_18px_contents position_absolute flex_justify_center invert" data-ui_pref="hide_sidebar" title="Toggle Sidebar">${ SVG_UI_Icons['toggle'] }</div> </div>`;
}
const directory_nav = `<nav id="directory_nav" class="display_flex_column background_grey_85"><div id="directory_nav_inner" class="position_relative border_bottom">
<div id="directory_list_outer" class="position_relative"><ol id="directory_list" class="display_flex_column margin_0 padding_0 text_color_default" tabindex="0">insert_prepped_index</ol></div>
</div></nav>`;
const directory_footer = `<footer id="directory_footer" class="display_flex_row position_relative background_grey_85 border_top text_color_default">insert_stats${ footer_utilities }</footer>`;
return `<div id="directory_wrapper" class="${body_id} display_flex_column position_relative border_right padding_0" style="width:${ Number(getSearchParam("width")) }%">
${ Directory_Header_Elements(body_id,parent_link) } ${ directory_nav } ${ directory_footer } ${ directory_utilities }
</div>`;
};
//==============================//
// CONTENT PANE HTML
// TEXT EDITING UI
const Text_Editing_UI_Elements = `
<div id="toolbar" class="border_bottom background_grey_80 position_relative text_color_default">
<ul id="toolbar_buttons" class="display_flex_row margin_0 padding_0"> <li id="text_editor_theme" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_theme" title="Toggle Editor Theme"><div class="display_flex width_14px_contents">${ SVG_Text_Editing_UI_Icons["toggle_theme"] }</div></li> <li id="toggle_text_editor_raw" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_raw" title="Show source"><div class="display_flex width_16px_contents">${ SVG_Text_Editing_UI_Icons["show_markdown"] }</div></li> <li id="toggle_text_editor_preview" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_preview" title="Show Preview"><div class="display_flex width_14px_contents">${ SVG_Text_Editing_UI_Icons["show_preview"] }</div></li> <li id="toggle_text_editor_html" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_html" title="Show formatted HTML"><div class="display_flex width_18px_contents">${ SVG_Text_Editing_UI_Icons["show_html"] }</div></li> <li id="toggle_text_editor_split_view" class="toggle_UI_pref toolbar_icon" data-ui_pref="text_editor_split_view" title="Toggle Split View"><div class="display_flex width_14px_contents">${ SVG_Text_Editing_UI_Icons["toggle_split"] }</div></li> <li id="text_editor_sync_scroll" class="toggle_UI_pref flex_justify_center_row" data-ui_pref="text_editor_sync_scroll"><input id="text_editor_sync_scroll_input" class="toggle_UI_pref flex_justify_center_row position_relative" data-ui_pref="text_editor_sync_scroll" name="text_editor_sync_scroll" type="checkbox"><label id="text_editor_sync_scroll_label" for="text_editor_sync_scroll" class="toggle_UI_pref flex_justify_center_row" data-ui_pref="text_editor_sync_scroll">Sync Scroll</label></li> <li class="display_flex flex_grow_1"> </li> <li id="clear_text" class="toolbar_icon" title="Clear Text">Clear</li> <li id="save_btn" class="toolbar_icon display_flex has_flyout_menu" title=""><div class="display_flex width_14px_contents">${ SVG_Text_Editing_UI_Icons['save_btn']}</div><ul class="menu background_grey_80 border_all"> <li id="save_text" class="item_1 border_right border_bottom background_grey_85" title="Save source text"><span id="save_text_link" target="_blank">Save Source</span></li> <li id="save_HTML" class="item_2 border_right background_grey_85" title="Save rendered html"><span id="save_HTML_link" target="_blank">Save HTML</span></li> </ul></li> </ul>
</div>
<div id="text_container" class="display_flex flex_grow_1"> <textarea id="text_editor_raw_pane" class=" text_container_pane margin_0 text_color_default display_none" tabindex="0"></textarea> <iframe id="text_editor_preview_pane" class=" text_container_pane margin_0 text_color_default display_none markdown_body" tabindex="0"></iframe> <textarea id="text_editor_html_pane" class=" text_container_pane margin_0 text_color_default display_none" tabindex="0" readonly></textarea>
<div id="text_editing_handle" class="position_absolute"></div></div>
`;
const Content_Audio_Elements = `
<div id="content_audio_title" class="display_flex background_grey_80 bold" title="Click to toggle .m3u playlist entry."><span></span></div>
<div id="content_audio" class="display_flex_row border_bottom background_grey_80">
<div id="audio_container" class="display_flex_row border_all">
<nav id="cue_sheet_track_list_container_audio" class="cue_sheet_track_list_container border_right" title="Cue sheet track list"> <div class="display_none position_absolute;"><ul id="cue_sheet_track_list_audio" class="cue_sheet_track_list background_grey_85 border_bottom margin_0 padding_0"> <li class="cue_sheet_track header display_grid background_grey_85 border_top border_bottom bold"> <span class="cue_track_id">Track</span><span class="cue_performer">Performer</span> <span class="cue_title">Title</span> <span class="cue_index" title="mm:ss:ff">Time</span> </li> </ul></div> </nav>
<div id="prev_track" class="prev_next_track_btn audio_controls flex_justify_center pointer" title="Previous track"><div class="display_flex width_24px_contents transform_rotate_180">${ SVG_UI_Icons['prev_next_track'] }</div></div>
<div id="next_track" class="prev_next_track_btn audio_controls flex_justify_center border_right pointer" title="Next track"><div class="display_flex width_24px_contents">${ SVG_UI_Icons['prev_next_track'] }</div></div>
<audio id="audio" preload="auto" tabindex="0" controls>Sorry, your browser does not support HTML5 audio.</audio>
<div id="close_audio" class="audio_controls border_left flex_justify_center position_relative pointer" title="Close audio"><div class="display_flex width_18px_contents">${ SVG_UI_Icons['multiply'] }</div></div>
<div id="audio_options" class="display_flex_column">
<label id="loop_label" for="loop"><input type="checkbox" id="loop" name="loop" tabindex="0" />Loop</label>
<label id="shuffle_label" for="shuffle"><input type="checkbox" id="shuffle" name="shuffle" tabindex="0" />Shuffle</label>
</div>
</div>
</div>
<div id="content_audio_playlist" class="playlist_entry_container border_bottom center padding_4_6 display_none"><textarea id="content_audio_playlist_textarea" rows="3" spellcheck="false"></textarea></div>
`;
const Content_Font_Viewer = '<div id="font_file_viewer" class="position_relative display_none"><div id="font_file_grid" class="position_relative padding_0 center display_grid"></div></div>';
// <li id="font_small_caps" class="flex_justify_center text hover_bold pointer" data-salt="smcp">Small Caps</li><li id="font_swash" class="flex_justify_center text hover_bold pointer" data-salt="swsh">Swash</li><li id="font_titling" class="flex_justify_center text hover_bold pointer" data-salt="ornm">Ornaments</li>
const Content_Font_Elements = function() {
const font_toolbar = `<div id="font_toolbar" class="display_none margin_0 position_fixed background_grey_80 border_bottom"><ol class="display_flex_row flex_grow_1">
<li id="font_variants" class="flex_justify_center" title="Font Variants"><select id="font_variant_select" label="Font Variants"> <option value="">OpenType Feature Tags</option>
<optgroup label="Caps"> <option value="normal">Normal</option> <option value="smcp" title="smcp">Small Caps</option> <option value="c2sc" title="c2sc">Capitals to Small Caps</option> <option value="pcap" title="pcap">Petite Caps</option> <option value="c2pc" title="c2pc">Capitals to Petite Caps</option> <option value="unic" title="unic">Unicase</option> <option value="cpsp" title="cpsp">Capital Spacing</option> <option value="case" title="case">Case Sensitive Forms</option> <option value="ital" title="ital">Italics</option> <option value="ordn" title="ordn">Ordinals</option> </optgroup>
<optgroup label="Numbers"> <option value="normal">Normal</option> <option value="pnum" title="pnum">Proportional Figures</option> <option value="tnum" title="tnum">Tabular Figures</option> <option value="frac" title="frac">Fractions</option> <option value="afrc" title="afrc">Alternative Fractions</option> <option value="dnom" title="dnom">Denominator</option> <option value="numr" title="numr">Numerator</option> <option value="sinf" title="sinf">Scientific Inferiors</option> <option value="zero" title="zero">Slashed Zero</option> <option value="mgrk" title="mgrk">Mathematical Greek</option> <option value="flac" title="flac">Flattened accent forms</option> <option value="dtls" title="dtls">Dotless Forms</option> <option value="ssty" title="ssty">Math script style alternates</option> </optgroup>
<optgroup label="Ligatures & Alts"> <option value="normal">Normal</option> <option value="aalt" title="aalt">Access All Alternates</option> <option value="swsh" title="swsh">Swash</option> <option value="cswh" title="cswh">Contextual Swash</option> <option value="calt" title="calt">Contextual Alternates</option> <option value="hist" title="hist">Historical Forms</option> <option value="locl" title="locl">Localized Forms</option> <option value="rand" title="rand">Randomize</option> <option value="nalt" title="nalt">Alternate Annotation Forms</option> <option value="cv01" title="cv01">Character Variant 1–99</option> <option value="salt" title="salt">Stylistic Alternates</option> <option value="ss01">Stylistic Set 1–20</option> <option value="subs" title="subs">Subscript</option> <option value="sups" title="sups">Superscript</option> <option value="titl" title="titl">Titling Alternates</option> <option value="rvrn" title="rvrn">Required Variation Alternates</option> <option value="clig" title="clig">Contextual Ligatures</option> <option value="dlig" title="dlig">Discretionary Ligatures</option> <option value="hlig" title="hlig">Historical Ligatures</option> <option value="liga" title="liga">Standard Ligatures</option> </optgroup>
<optgroup label="Special Features"> <option value="normal">Normal</option> <option value="size" title="size">Optical size</option> <option value="ornm" title="ornm">Ornaments</option> </optgroup>
<optgroup label="Indic/Brahmic"> <option value="normal">Normal</option> <option value="abvf" title="abvf">Above-base Forms</option> <option value="abvm" title="abvm">Above-base Mark Positioning</option> <option value="abvs" title="abvs">Above-base Substitutions</option> <option value="blwf" title="blwf">Below-base Forms</option> <option value="blwm" title="blwm">Below-base Mark Positioning</option> <option value="blws" title="blws">Below-base Substitutions</option> <option value="pref" title="pref">Pre-base Forms</option> <option value="pres" title="pres">Pre-base Substitutions</option> <option value="psts" title="psts">Post-base Substitutions</option> <option value="pstf" title="pstf">Post-base Forms</option> <option value="dist" title="dist">Distance</option> <option value="akhn" title="akhn">Akhand</option> <option value="haln" title="haln">Halant Forms</option> <option value="half" title="half">Half Form</option> <option value="nukt" title="nukt">Nukta Forms</option> <option value="rkrf" title="rkrf">Rakar Forms</option> <option value="rphf" title="rphf">Reph Form</option> <option value="vatu" title="vatu">Vattu Variants</option> <option value="cjct" title="cjct">Conjunct Forms</option> <option value="cfar" title="cfar">Conjunct Form After Ro</option> </optgroup>
<optgroup label="East Asian"> <option value="normal">Normal</option> <option value="smpl" title="smpl">Simplified Forms</option> <option value="trad" title="trad">Traditional Forms</option> <option value="tnam" title="tnam">Traditional Name Forms</option> <option value="expt" title="expt">Expert Forms</option> <option value="hojo" title="hojo">Hojo Kanji Forms</option> <option value="nlck" title="nlck">NLC Kanji Forms</option> <option value="jp78" title="jp78">JIS 78 Forms</option> <option value="jp83" title="jp83">JIS 83 Forms</option> <option value="jp90" title="jp90">JIS 90 Forms</option> <option value="jp04" title="jp04">JIS 04 Forms</option> <option value="hngl" title="hngl">Hangul</option> <option value="ljmo" title="ljmo">Leading Jamo Forms</option> <option value="tjmo" title="tjmo">Trailing Jamo Forms</option> <option value="vjmo" title="vjmo">Vowel Jamo Forms</option> <option value="fwid" title="fwid">Full Widths</option> <option value="hwid" title="hwid">Half Widths</option> <option value="halt" title="halt">Alternate Half Widths</option> <option value="twid" title="twid">Third Widths</option> <option value="qwid" title="qwid">Quarter Widths</option> <option value="pwid" title="pwid">Proportional Widths</option> <option value="palt" title="palt">Proportional Alternates</option> <option value="pkna" title="pkna">Proportional Kana</option> <option value="ruby" title="ruby">Ruby Notation Forms</option> <option value="hkna" title="hkna">Horizontal Kana Alternates</option> <option value="vkna" title="vkna">Vertical Kana</option> <option value="cpct" title="cpct">Centered CJK Punctuation</option> </optgroup>
<optgroup label="West Asian"> <option value="normal">Normal</option> <option value="curs" title="curs">Cursive Positioning</option> <option value="jalt" title="jalt">Justification Alternates</option> <option value="mset" title="mset">Mark Positioning via Substitution</option> <option value="rclt" title="rclt">Required Contextual Alternates</option> <option value="rlig" title="rlig">Required Ligatures</option> <option value="isol" title="isol">Isolated Forms</option> <option value="init" title="init">Initial Forms</option> <option value="medi" title="medi">Medial Forms</option> <option value="med2" title="med2">Medial Form #2</option> <option value="fina" title="fina">Terminal Forms</option> <option value="fin2" title="fin2">Terminal Form #2</option> <option value="fin3" title="fin3">Terminal Form #3</option> <option value="falt" title="falt">Final Glyph on Line Alternates</option> <option value="stch" title="stch">Stretching Glyph Decomposition</option> </optgroup>
<optgroup label="Positioning"> <option value="normal">Normal</option> <option value="ccmp" title="ccmp">Glyph Composition/Decomposition</option> <option value="kern" title="kern">Kerning</option> <option value="mark" title="mark">Mark Positioning</option> <option value="mkmk" title="mkmk">Mark-to-mark Positioning</option> <option value="opbd" title="opbd">Optical Bounds</option> <option value="lfbd" title="lfbd">Left Bounds</option> <option value="rtbd" title="rtbd">Right Bounds</option>/option> </optgroup>
</select></li>
<li id="font_stylistic_set" class="flex_justify_center" data-salt="ssnn" title="Stylistic Sets"> <select id="font_stylistic_set_select" class=""> <option value="ss00">Stylistic Sets</option> <option value="ss01">ss01</option> <option value="ss02">ss02</option> <option value="ss03">ss03</option> <option value="ss04">ss04</option> <option value="ss05">ss05</option> <option value="ss06">ss06</option> <option value="ss07">ss07</option> <option value="ss08">ss08</option> <option value="ss09">ss09</option> <option value="ss10">ss10</option> <option value="ss11">ss11</option> <option value="ss12">ss12</option> <option value="ss13">ss13</option> <option value="ss14">ss14</option> <option value="ss15">ss15</option> <option value="ss16">ss16</option> <option value="ss17">ss17</option> <option value="ss18">ss18</option> <option value="ss19">ss19</option> <option value="ss20">ss20</option> </select></li>
<li id="font_tag" class="flex_justify_center" data-salt=""><textarea id="font_tag_textarea" rows="1" cols="4" spellcheck="false" maxlength="4" placeholder="otftag" title="Enter an OpenType Feature Tag (e.g.: “smcp”)"></textarea></li>
<li class="spacer width_100"></li>
<li id="unicode_char_planes" class="flex_justify_center" title="Unicode Code Ranges"><select id="unicode_char_ranges_select">
<option value="">Unicode Code Ranges</option>
<optgroup label="Basic Multilingual Plane"> <option value="BMP_Range_01">BMP-01: U+0000–U+0FFF</option> <option value="BMP_Range_02">BMP-02: U+1000–U+1FFF</option> <option value="BMP_Range_03">BMP-03: U+2000–U+2FFF</option> <option value="BMP_Range_04">BMP-04: U+3000–U+3FFF</option> <option value="BMP_Range_05">BMP-05: U+4000–U+4FFF</option> <option value="BMP_Range_06">BMP-06: U+5000–U+5FFF</option> <option value="BMP_Range_07">BMP-07: U+6000–U+6FFF</option> <option value="BMP_Range_08">BMP-08: U+7000–U+7FFF</option> <option value="BMP_Range_09">BMP-09: U+8000–U+8FFF</option> <option value="BMP_Range_10">BMP-10: U+9000–U+9FFF</option> <option value="BMP_Range_11">BMP-11: U+A000–U+AFFF</option> <option value="BMP_Range_12">BMP-12: U+B000–U+BFFF</option> <option value="BMP_Range_13">BMP-13: U+C000–U+CFFF</option> <option value="BMP_Range_14">BMP-14: U+D000–U+DFFF</option> <option value="BMP_Range_15">BMP-15: U+E000–U+EFFF</option> <option value="BMP_Range_16">BMP-16: U+F000–U+FFFF</option> </optgroup>
<optgroup label="Supplementary Multilingual Plane"> <option value="SMP_Range_01">SMP-01: U+10000–U+10FFF</option> <option value="SMP_Range_02">SMP-02: U+11000–U+11FFF</option> <option value="SMP_Range_03">SMP-03: U+12000–U+12FFF</option> <option value="SMP_Range_04">SMP-04: U+13000–U+13FFF</option> <option value="SMP_Range_05">SMP-05: U+14000–U+14FFF</option> <option value="SMP_Range_06">SMP-06: U+15000–U+15FFF</option> <option value="SMP_Range_07">SMP-07: U+16000–U+16FFF</option> <option value="SMP_Range_08">SMP-08: U+17000–U+17FFF</option> <option value="SMP_Range_09">SMP-09: U+18000–U+18FFF</option> <option value="SMP_Range_10">SMP-10: U+19000–U+19FFF</option> <option value="SMP_Range_11">SMP-11: U+1A000–U+1AFFF</option> <option value="SMP_Range_12">SMP-12: U+1B000–U+1BFFF</option> <option value="SMP_Range_13">SMP-13: U+1C000–U+1CFFF</option> <option value="SMP_Range_14">SMP-14: U+1D000–U+1DFFF</option> <option value="SMP_Range_15">SMP-15: U+1E000–U+1EFFF</option> <option value="SMP_Range_16">SMP-16: U+1F000–U+1FFFF</option> </optgroup>
<optgroup label="Supplementary Ideographic Plane"> <option value="SIP_Range_01">SIP-01: U+20000–U+20FFF</option> <option value="SIP_Range_02">SIP-02: U+21000–U+21FFF</option> <option value="SIP_Range_03">SIP-03: U+22000–U+22FFF</option> <option value="SIP_Range_04">SIP-04: U+23000–U+23FFF</option> <option value="SIP_Range_05">SIP-05: U+24000–U+24FFF</option> <option value="SIP_Range_06">SIP-06: U+25000–U+25FFF</option> <option value="SIP_Range_07">SIP-07: U+26000–U+26FFF</option> <option value="SIP_Range_08">SIP-08: U+27000–U+27FFF</option> <option value="SIP_Range_09">SIP-09: U+28000–U+28FFF</option> <option value="SIP_Range_10">SIP-10: U+29000–U+29FFF</option> <option value="SIP_Range_11">SIP-11: U+2A000–U+2AFFF</option> <option value="SIP_Range_12">SIP-12: U+2B000–U+2BFFF</option> <option value="SIP_Range_13">SIP-13: U+2C000–U+2CFFF</option> <option value="SIP_Range_14">SIP-14: U+2D000–U+2DFFF</option> <option value="SIP_Range_15">SIP-15: U+2E000–U+2EFFF</option> <option value="SIP_Range_16">SIP-16: U+2F000–U+2FFFF</option> </optgroup>
<optgroup label="Tertiary Ideographic Plane"> <option value="TIP_Range_01">TIP-01: U+30000–U+30FFF</option> <option value="TIP_Range_02">TIP-02: U+31000–U+31FFF</option> </optgroup>
<optgroup label="Supplementary Special-Purpose Plane"> <option value="SSP_Range_01">SSP-01: U+E0000–U+E0FFF</option> </optgroup>
</select></li>
</ol></div>
`;
const sample_string = `ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789<br />!"#$%&'()*+,-./:;<=>?@[\\]^_\`{|}~`;
const lorem_string = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
return `${ font_toolbar }<div id="font_specimen" class="display_none">
<div id="font_specimen_grid" class="border_left_x border_top_x"></div><hr />
<div id="specimen" class="specimen" contenteditable="true" tabindex="0">${ sample_string }</div><hr />
<div id="specimen_string_2" class="specimen" contenteditable="true" tabindex="0"><div id="specimen_2">Typography</div><div id="specimen_2H4">The art of using types to produce impressions on paper, vellum, &c.</div></div><hr />
<div id="specimen_string_3" class="specimen" contenteditable="true"><div id="specimen_3">S P E C I M E N</div><div id="specimen_3H3">Typography is the work of typesetters (also known as compositors), typographers, graphic designers, art directors, manga artists, comic book artists, graffiti artists, and, now, anyone who arranges words, letters, numbers, and symbols for publication, display, or distribution.</div></div><hr />
<div id="lorem_strings" class="specimen" contenteditable="true" tabindex="0"> <div id="lorem" class="lorem">${ lorem_string }</div><div id="lorem_2" class="lorem">${ lorem_string }</div><div id="lorem_3" class="lorem">${ lorem_string }</div></div>
<div id="specimen_glyph_container" class="background_grey_100"><div id="specimen_glyph" class="flex_justify_center position_fixed"></div><div id="specimen_glyph_overlay" class="position_fixed background_grey_100"></div></div>
</div>
${ Content_Font_Viewer }
<div id="glyph_viewer" class="invert margin_0 padding_0 position_absolute display_none"><div id="glyph_viewer_info" class="background_grey_85 border_bottom_x padding_4_6 invert"><button id="save_svg_hidden">Save SVG</button><div class="flex_justify_center_row"></div><button id="save_svg">Save SVG</button></div></div>
`;
};
const Content_Header_Elements = `
<header id="content_header">
<div id="audio_wrapper" class="text_color_default background_grey_75 display_none"> ${ Content_Audio_Elements } </div>
<div id="content_title_container" class="title display_flex flex_justify_contents text_color_default border_bottom pointer">
<div id="title_buttons_left" class="display_flex padding_4_6 alighn_left">
<nav id="cue_sheet_track_list_container_video" class="cue_sheet_track_list_container border_right background_grey_75" title="Cue sheet track list"></nav>
<button id="reload_btn" tabindex="-1"><span></span></button> <button id="prev_next_btns" class="split_btn padding_0 position_relative display_none" tabindex="-1"><span id="prev_btn" class="flex_justify_center"><span class="transform_rotate_270_contents">${ SVG_UI_Icons['chevron'] }</span></span><span id="next_btn" class="flex_justify_center"><span class="display_flex transform_rotate_90_contents">${ SVG_UI_Icons['chevron'] }</span></span></button>
</div>
<div id="content_title" class=""><span class="has_icon_before bold"></span></div>
<div id="title_buttons_right" class="display_flex padding_4_6 align_right"> <button id="scale" class="split_btn padding_0 position_relative display_none" tabindex="-1"><span id="increase" class="flex_justify_center" title="Enlarge"><div class="display_flex width_10px_contents">${ SVG_UI_Icons['plus'] }</div></span><span id="decrease" class="flex_justify_center" title="Reduce"><div class="display_flex width_10px_contents">${ SVG_UI_Icons['minus'] }</div></span></button> <button id="open_in_text_editor" class="display_none" title="Open in Text Editor" tabindex="-1"><span>Edit</span></button> <button id="close_btn" tabindex="-1" title="Close Content"><span></span></button> </div>
</div>
<ul id="cue_sheet_track_list_video" class="cue_sheet_track_list border_bottom margin_0 padding_0 display_none">
<li class="cue_sheet_track header display_grid background_grey_85 border_top border_bottom bold"> <span class="cue_track_id">Track</span><span class="cue_performer">Performer</span> <span class="cue_title">Title</span> <span class="cue_index" title="mm:ss:ff">Time</span> </li>
</ul>
<div id="content_playlist" class="playlist_entry_container border_bottom center display_none"><textarea id="content_playlist_textarea" rows="3" spellcheck="false"></textarea></div>
</header>`;
// CONTENT containers
const Content_Pane_Elements = `<div id="content_pane" class="display_flex_column flex_grow_1 position_relative" data-content="has_null">
${ Content_Header_Elements }
<main id="content_container" class="display_flex position_relative background_grey_85 margin_0 padding_0"> ${ SVG_UI_Icons['spinner'] } <div id="content_grid" class="content" data-kind="grid"></div> <div id="content_text" class="background_grey_85 margin_0 padding_0 position_absolute"></div> <div id="content_font" class="content background_grey_100 position_relative text_color_default" spellcheck="false" data-kind="font">${ Content_Font_Elements() }</div> <div id="content_image_container" class="content background_grey_95 position_relative margin_0" data-kind="image"><img id="content_image" class="position_relative" src="#" alt="" tabindex="0" /></div> <embed id="content_pdf" class="content position_relative" tabindex="0" data-kind="pdf"> <video id="content_video" class="content background_grey_95 media position_absolute" controls data-kind="video">Your browser does not support the video tag.</video> <iframe id="content_iframe" class="content position_relative" name="content_iframe" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" tabindex="0"></iframe> <iframe id="content_iframe_utility" class="content" name="content_iframe_utility" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" tabindex="0"></iframe> </main>
</div>`;
//==============================//
// UTILITIES HTML (warnings and help)
const Utilities_Warnings = `
<header id="warnings_header" class="text_color_default background_grey_85"><h3 id="warning_header"><span>Warning:</span></h3><h3 id="make_playlist_header"><span>Make Playlist/Filelist (.m3u)</span></h3></header>
<div id="warnings" class="text_color_default background_grey_85">
<div id="warning_close_font" class="warning">Are you sure you want to close the font file?</div> <div id="warning_unsaved_text" class="warning">You have unsaved changes.</div> <div id="warning_clear_text" class="warning">Are you sure you want to clear all your text?</div> <div id="warning_local_bookmark" class="warning">Can’t load local items from non-local pages. <br /> Please use your browser’s bookmarks instead or enter the URL manually.</div> <div id="warning_local_file" class="warning">Can’t load local items from non-local pages.</div> <div id="warning_close_playlist" class="warning">Are you sure you want to close the playlist?</div> <div id="warning_local_playlist" class="warning">This playlist contains local files. <br /> Please reload this playlist from a local page in order to play them.</div> <div id="warning_no_playlist" class="warning">Can’t make playlist: no qualified items found.</div>
<div id="warning_make_playlist" class="warning">
<form id="make_playlist_form" action="#"><fieldset class="margin_0 padding_0"> <div><input name="make_playlist" type="radio" id="media_files_only" checked><label for="media_files_only">All media files</label></div> <div class="indent"><input name="make_playlist" type="radio" id="audio_files_only"><label for="audio_files_only">Audio files only</label></div> <div class="indent"><input name="make_playlist" type="radio" id="video_files_only"><label for="video_files_only">Video files only</label></div> <div><input name="make_playlist" type="radio" id="all_non_media_files"><label for="all_non_media_files">All non-media items</label></div> <div><input name="make_playlist" type="radio" id="all_items"><label for="all_items">All items</label></div> <div class="indent"><input name="make_playlist" type="radio" id="directories_only"><label for="directories_only">Directories only</label></div> <div class="indent"><input name="make_playlist" type="radio" id="files_only"><label for="files_only">Files only</label></div> </fieldset>
</form>
</div>
</div>
<div id="warning_buttons_container" class="display_flex_column background_grey_90"> <div id="warning_buttons" class="display_flex_row"> <button id="warning_btn_dont_save">Don’t Save</button> <button id="warning_btn_cancel" >Cancel</button> <button id="warning_btn_clear">Clear</button> <button id="warning_btn_save">Save</button> <button id="warning_btn_ok">OK</button> </div> </div>
`;
const Utilities_Help = `
<header id="help_header" class="title display_grid padding_4_6 text_color_default border_bottom_x background_grey_75 center"><span class="spacer"></span><span class="bold flex_justify_center">HELP</span><button id="close_help" class="focus"><span>Close</span></button></header><section><p class="center bold"><a href="https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser" class="has_icon_before link" target="_blank">SCRIPT HOME: openuserjs.org</a></p>
<ul id="utilities_help" class="info_list"> <li class="info_list_header center bold"><span class="col_1">KEYBOARD SHORTCUTS</span><span class="col_2">DESCRIPTION</span></li> <li><span class="col_1"><kbd>↑</kbd> or <kbd>↓</kbd></span><span class="col_2">Select the previous/next sidebar item or previewed directory item.<br />If audio is playing, and the previous/next file is also audio, the file will be highlighted but not loaded in the audio player; press <kbd>return</kbd> to load it.</span></li> <li><span class="col_1"><kbd>←</kbd> or <kbd>→</kbd></span><span class="col_2">Select prev/next item of the same kind as the current selection.<br />If current selection is a media file, select and begin playback of the next media item.</span></li> <li><span class="col_1"><kbd>⌥</kbd> + <kbd>←</kbd> or <kbd>→</kbd></span><span class="col_2">Skip media ±10s</span></li> <li><span class="col_1"><kbd>⌥</kbd> + <kbd>⇧</kbd> + <kbd>←</kbd> or <kbd>→</kbd></span><span class="col_2">Skip skip ±30s</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>↑</kbd></span><span class="col_2">Go to parent directory</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>↓</kbd></span><span class="col_2">Open selected sidebar directory</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>→</kbd></span><span class="col_2">Open selected subdirectory in sidebar.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>←</kbd></span><span class="col_2">Close selected subdirectory in sidebar or jump to parent directory.</span></li> <li><span class="col_1"><kbd>Escape</kbd></span><span class="col_2">Close menus and help, unfocus textareas and content pane, etc.</span></li> <li><span class="col_1"><kbd>Return</kbd></span><span class="col_2">Open selected directory, select file, or pause/play media.</span></li> <li><span class="col_1"><kbd>Space</kbd></span><span class="col_2">Pause/Play media files (if media player loaded).</span></li> <li><span class="col_1"><kbd>Tab</kbd></span><span class="col_2">Toggle focus between sidebar and content pane.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>D</kbd></span><span class="col_2">Toggle file details (size, date modified, kind) in some index page types.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>E</kbd></span><span class="col_2">Toggle main menu.</span></li> <li><span class="col_1"><kbd>⇧</kbd> + <kbd>⌘/Ctrl</kbd> + <kbd>E</kbd></span><span class="col_2">Show text editor.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>G</kbd></span><span class="col_2">Show or reload image or font grids.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>I</kbd></span><span class="col_2">Toggle invisible files.</span></li> <li><span class="col_1"><kbd>⇧</kbd> + <kbd>⌘/Ctrl</kbd> + <kbd>O</kbd></span><span class="col_2">Open selected sidebar item in new window/tab.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>R</kbd></span><span class="col_2">Reload grids and previewed content, reset scaled images/fonts, reset media files to beginning.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>W</kbd></span><span class="col_2">Close previewed content (doesn’t work in all browsers; use close button instead), or close window if no content is being previewed.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>⇧</kbd> + <kbd><</kbd> or <kbd>></kbd></span><span class="col_2">Scale preview items and grids.</span></li> <li><span class="col_1"><kbd>⌘/Ctrl</kbd> + <kbd>\\</kbd></span><span class="col_2">Toggle sidebar.</span></li> <li><span class="col_1"><kbd>⇧</kbd> + <kbd>⌘/Ctrl</kbd> + <kbd>\\</kbd></span><span class="col_2">Toggle text editor split view.</span></li> </ul>
<div id="help_main_menu"><div>${ SVG_UI_Icons['menu'] }</div><strong>MAIN MENU</strong><dl><dt></dt><dd>Selecting many menu items will override the hard-coded default preferences in the script by adding an entry to the query string in the browser window URL.</dd><dd>This makes it more convenient to customize preferences, since defaults changed in the code will be over-written whenever the script is updated.</dd></dl><dl><dt id="help_bookmarks" class="has_icon_before">BOOKMARKS</dt><dd>You can add bookmarks to files or directories to this menu by editing the User Preference in the code.</dd><dd>However, any items you add will be over-written when the script is updated. To save them, you can "Export User Settings" (see below) before updating the script.</dd><dd>Of course, you may simply use your browser's bookmarks instead.</dd></dl>
<dl><dt>SORT BY...</dt><dd>Sort directory items by Name, Size, Date, Kind, Extension, Duration (media items only), or Default (i.e., sorted by Name, with files on top)</dd></dl>
<dl><dt>LIGHT THEME/DARK THEME</dt><dd>Change the UI theme.</dd></dl>
<dl><dt>ALTERNATE BACKGROUNDS</dt><dd>Alternate background colors for directory list items.</dd></dl>
<dl><dt>SHOW NUMBERS</dt><dd>Show numbers for directory list items.</dd></dl>
<dl><dt>IGNORED ITEMS</dt><dd>Ignored items include files that the browser cannot handle natively, e.g., common Office and graphics files, various binary files, and many others.</dd><dd>Although they are visible in the sidebar by default, and ignored when selected, they can be hidden here.</dd><dd>Also, normal browser behavior for handling such files can be restored by unchecking "Ignore Ignored Files", but it is recommended that this preference be left checked.</dd></dl>
<dl><dt>AUTOLOAD FILES</dt><dd> XXX </dd></dl>
<dl><dt>MEDIA FILES</dt><dd> XXX </dd></dl>
<dl><dt>OPEN PLAYLIST/FILELIST</dt><dd>The script supports basic .m3u playlists of audio or video files. Click to load a local .m3u file.</dd><dd>The script also has custom support for "filelists," which are standard .m3u files that contain links to directories and <i>any</i> file type supported by the script or browser.</dd><dd>Note: if you change the extension of an ordinary .m3u file to .txt, the script will read it normally as an editable text file.<br />Double-clicking the selected file in the sidebar, or typing <code>Cmd/Ctr + Down Arrow</code> or <code>Cmd/Ctr + Return</code>, will open the playlist/filelist in the sidebar.<br />NOTE: The text must begin with #EXTM3U for this work.</dd></dl> <dl><dt>SAVE PLAYLIST/FILELIST</dt><dd>Save the files in the current sidebar directory as an .m3u playlist/filelist. Choose audio, video, all media, all non-media, all items, directories or files only.</dd></dl>
<dl><dt>TEXT EDITING ENABLED/DISABLED</dt><dd>If text editing is disabled, text files are displayed as normal files.</dd></dl>
<dl><dt>TEXT EDITING OPTIONS</dt><dd>Toggle the Text Editor. Select Editor UI theme: Default = same as main UI. Toggle split view. Select view of raw text, preview text, rendered HTML.</dd></dl>
<dl><dt>OPEN FONT FILE</dt><dd>Load a local font file and view information about the font and a grid of its complete glyph repertoire.</dd><dd>Glyph grids can be navigated with the arrow keys. Individual glyphs can be selected by clicking them or pressing <kbd>Return</kbd>.</dd><dd>Individual glyphs may be saved as .svg files.</dd></dl> <dl><dt>DEFAULT USER SETTINGS</dt><dd>This will remove the query string from the browser window URL, so that the default user settings from the code will be used instead.</dd></dl> <dl><dt>EXPORT USER SETTINGS</dt><dd>Save a file containing the User Settings from the code, including custom bookmarks and other settings.</dd></dl></div>
<div><strong>OTHER SCRIPT FUNCTIONS</strong><dl><dt>NAVIGATION</dt><dd>Sidebar displays list of items in current directory. Select a sidebar item to show it in the content pane.</dd><dd>Click a directory icon in the sidebar or select it and type Cmd/Ctrl Right Arrow to open subdirectory; click again to close or type Cmd/Ctrl Left Arrow.</dd></dl><dl><dt>IMAGES, FONTS, FONT GLYPHS</dt><dd>Previewed items can be scaled with <kbd>Cmd/Ctr +/–</kbd> keys.</dd></dl> <dl><dt>IMAGE AND FONT GRIDS</dt><dd>If a directory contains images and/or font files, the "Show Grid" icon will appear in the sidebar. Click it (or type <kbd>Cmd/Ctr+G</kbd>) to show a grid of the available items.</dd><dd>Grids can be navigated with the arrow keys, and individual grid items may be viewed by clicking them or pressing <kbd>Return</kbd>.</dd><dd>When a grid item is being viewed, the grid can still be navigated with the arrow keys.</dd><dd>Closing a selected grid item will show the grid again.</dd></dl> <dl><dt>CUE SHEETS</dt><dd>When a media file (audio or video) is loaded, the script will look for a .cue file in the same directory with <i>EXACTLY</i> the same name as the media file.</dd><dd>If it finds one, it will load the Track ID, the PERFORMER, the TITLE, and the INDEX (time position) into a menu next to the audio player; there is no support for other commands.</dd><dd>Tracks can be selected by clicking the item, and played or paused by clicking the selected item.</dd><dd>.cue files can also be selected independently in the sidebar and edited and saved (locally). This may be handy for creating "on the fly" bookmarks for a long media track before closing the page.</dd><dd>Note that you can also create and save (locally) a new .cue file by using the Text Editor.</dd><dd>Note (MacOS): If you prefer not to clutter the sidebar with .cue files, you may make them invisible by adding a dot to beginning of the file name; the script will still find them.</dd></dl><p> </p>
</div></section>`;
let Utilities_Elements = function(body_id) {
let help_elements = ''; if ( body_id === 'top' ) { help_elements = `<aside id="help_container" class="background_grey_90 display_none">${ Utilities_Help }</aside>`; }
return `<div id="utilities" class="display_none"> <aside id="warnings_container">${ Utilities_Warnings }</aside> ${ help_elements } </div>`;
}
//==============================//
// ===> END UI HTML
//==============================//
// ===> STYLES
const global_styles = `
:root { --font_size_small:0.875rem; }
:root, html, body { margin:0; padding:0; border:0; border-radius:0; overflow:hidden; display:flex; flex-direction:row; width:100%; max-width:100%; height:100vh; font-family:${$settings.UI_font}; font-size:${ $settings.UI_font_size}; hyphens:auto; }
header, footer, nav, ol, ul, li { margin:0; }
header, footer, nav, a, ol, ul, li { padding:0; }
a, a:hover { text-decoration:none !important; }
ul, li { list-style:none; }
.margin_0 { margin:0; }
.padding_0 { padding:0; }
.padding_4_6 { padding:4px 6px; }
.width_10px, .width_10px_contents > * { width:10px; max-width:10px; min-width:10px; }
.width_12px, .width_12px_contents > * { width:12px; max-width:12px; min-width:12px; }
.width_14px, .width_14px_contents > * { width:14px; max-width:14px; min-width:14px; }
.width_16px, .width_16px_contents > * { width:16px; max-width:16px; min-width:16px; }
.width_18px, .width_18px_contents > * { width:18px; max-width:18px; min-width:18px; }
.width_24px, .width_24px_contents > * { width:24px; max-width:24px; min-width:24px; }
.width_100 { width:100%; }
.position_absolute { position:absolute; }
.position_relative { position:relative; }
.position_fixed { position:fixed; }
.display_none { display:none; }
.display_block { display:block; }
.display_flex { display:flex; }
.display_flex_column { display:flex; flex-direction:column; }
.display_flex_row { display:flex; flex-direction:row; }
.flex_justify_center { display:flex; flex-direction:column; justify-content:center; flex-grow:1; align-items:center; align-self:stretch; text-align:center; }
.flex_justify_center_row { display:flex; flex-direction:row; justify-content:center; flex-grow:1; align-items:center; }
.flex_justify_contents { justify-content:space-between; }
.flex_grow_1 { flex-grow:1; }
.display_grid { display:grid; }
.transform_rotate_90, .transform_rotate_90_contents > * { transform:rotate(90deg); }
.transform_rotate_180, .transform_rotate_180_contents > * { transform:rotate(180deg); }
.transform_rotate_270, .transform_rotate_270_contents > * { transform:rotate(270deg); }
svg { margin:auto; }
.theme_dark .invert { filter:invert(1); }
.normal { font-weight:normal; }
.bold, .hover_bold:hover { font-weight:bold; }
.align_left { text-align:left; }
.center { text-align:center; }
.align_right { text-align:right; }
.pointer, label, input { cursor:pointer; }
.media.local input { cursor:not-allowed; }
.info_list { padding:0; background-color:hsl(0,0%,var(--percent_80)); font-size:var(--font_size_small); color:hsl(0,0%,var(--percent_10)); border:solid 1px hsl(0,0%,var(--border_lum)); }
.info_list li { grid-template-columns:minmax(33%,100%) min(66%); border-top:solid 1px hsl(0,0%,var(--border_lum)); }
.info_list:hover li { display:grid; }
.info_list li.info_list_header { border-top:none; }
.info_list li .col_1 { font-weight:bold; text-align:right; border-right:solid 1px hsl(0,0%,var(--border_lum)); }
.info_list span { display:inline-block; padding:4px 6px; line-height:1.2; }
.has_flyout_menu { display:none; position:relative; outline:none; justify-content:center; align-content:center; z-index:9997; }
.has_flyout_menu ul { display:none; position:absolute; list-style:none; }
.has_flyout_menu:hover ul { display:grid; }
.has_flyout_menu ul li { width:100%; margin:0; padding:4px 6px; text-align:right; box-sizing:border-box; white-space:pre; grid-column:1; }
.has_flyout_menu ul li:hover { font-weight:bold; }
.has_flyout_menu ul div { grid-column:2; padding-top:7px; }
.has_flyout_menu .item_1 { grid-row:1; }
.has_flyout_menu .item_2 { grid-row:2; }
.border_all { border: solid 1px hsl(0,0%,var(--border_lum)); }
.border_top { border-top: solid 1px hsl(0,0%,var(--border_lum)); }
.border_right { border-right: solid 1px hsl(0,0%,var(--border_lum)); }
.border_bottom { border-bottom: solid 1px hsl(0,0%,var(--border_lum)); }
.border_left { border-left: solid 1px hsl(0,0%,var(--border_lum)); }
.border_top_x { border-top: solid 1px hsl(0,0%,var(--border_lum_inverted)); } /* "x" = inverted for theme_dark */
.border_right_x { border-right: solid 1px hsl(0,0%,var(--border_lum_inverted)); }
.border_bottom_x { border-bottom: solid 1px hsl(0,0%,var(--border_lum_inverted)); }
.border_left_x { border-left: solid 1px hsl(0,0%,var(--border_lum_inverted)); }
`;
const utilities_styles = `
#utilities { position:absolute; left:0; right:0; top:0; z-index:9999; justify-content:center; }
#warnings_container { width:26em; flex-direction:column; border-radius:0 0 3px 3px; z-index:9999; background-color:hsl(0,0%,var(--percent_90)); box-shadow:0px 2px 12px 0 #333; font-size:0.875em; color:#111; overflow:hidden; display:none; }
#warnings_header { padding:1rem 1.5rem; background-position:left 1.25rem center; background-repeat:no-repeat; background-size:24px; }
#warnings_container:not(.warning_make_playlist) #warnings_header { background-image:${ get_SVG_UI_Icon("error") }; }
#warnings_header h3 { display:none; margin:0; text-indent:2.25em; }
#warnings_container:not(.warning_make_playlist) h3#warning_header, #warnings_container.warning_make_playlist h3#make_playlist_header
{ display:flex;}
body#top.edited #warnings_container.unloading h3::before { content:"Text Editor:" }
#warnings .warning { padding:0 1.5rem 1rem; display:none; hyphens:none; }
#warning_buttons_container { padding:1rem 1.5rem; }
#warning_buttons button { min-width:4em; display:none; font-size:1em; justify-content:center; }
button.focus, button:focus { background-color:#0E4399; color:#EEE; outline:none; }
#warning_btn_dont_save { margin-right:auto; }
#warning_btn_cancel, #warning_btn_clear, #warning_btn_save { margin-left:0.5rem; }
#warning_btn_ok { margin-left:auto; }
#warnings_container.warning_close_font #warning_close_font, #warnings_container.warning_close_playlist #warning_close_playlist, #warnings_container.unloading #warning_unsaved_text, #warnings_container.clear #warning_clear_text, #warnings_container.warning_local_bookmark #warning_local_bookmark, #warnings_container.warning_local_file #warning_local_file, #warnings_container.warning_no_playlist #warning_no_playlist, #warnings_container.warning_make_playlist #warning_make_playlist,
#warnings_container:not(.unloading):not(.clear) #warning_btn_ok, #warnings_container:not(.warning_local_bookmark):not(.warning_local_file):not(.warning_no_playlist) #warning_btn_cancel, #warnings_container.clear #warning_btn_clear, #warnings_container.unloading #warning_btn_dont_save
{ display:flex; }
#warnings_container.clear #warning_buttons { justify-content:space-between; }
#warnings_container.warning_local_bookmark #warning_local_bookmark, #warnings_container.warning_local_bookmark #warning_btn_ok, #warnings_container.warning_local_file #warning_local_file, #warnings_container.warning_local_file #warning_btn_ok, #warnings_container.warning_close_font #warning_btn_ok, #warnings_container.warning_close_playlist #warning_btn_ok { margin-left:auto; }
#warning_make_playlist fieldset { border:0; }
#warning_make_playlist fieldset div { padding:0 0 2px; }
#warning_make_playlist .indent { text-indent:2em; }
#warning_make_playlist input { margin-right:6px; }
#warnings_container.warning_local_playlist #warning_local_playlist, #warnings_container.warning_local_playlist #warning_btn_ok { display:inline-block; margin-left:auto; }
.has_warning #utilities, .has_warning #warnings_container { display:flex; }
.has_warning::before, .has_overlay::before { content:""; position:fixed; top:0; right:0; bottom:0; left:0; z-index:9998;-webkit-user-select:none;-moz-user-select:none; user-select:none; }
#help_container { padding:0 1em 1em; overflow:auto; }
#help_container > header { position:fixed; left:0; right:0; grid-template-columns:5em auto fit-content(100%); }
#help_container > section { padding-top:2rem; }
#help_container > section > div { margin-top:1rem; padding-top:1rem; border-top:solid 1px hsl(0,0%,var(--border_lum)); }
#help_container dd { margin-inline-start:1em; }
#help_container dd:before { content:"\u2219"; margin-right:6px; }
#utilities_help kbd { min-width:1em; height:fit-content; padding:2px 6px; display:inline-block; border:solid 1px #888; border-radius:3px; text-align:center; font-family:inherit; font-size:0.875em; background-color:hsl(0,0%,var(--percent_90)); }
#help_main_menu div { margin-right:6px; width:14px; display:inline-flex; }
#help_bookmarks::before { background-image: ${ get_SVG_UI_Icon("bookmark") }; }
.has_help #utilities, .has_help #help_container { display:flex; bottom:0; }
`;
const directory_header_menu_styles = `
/* PARENTS MENU */
#parent_dir_nav svg { width:18px; }
#close_playlist_btn { background-image:${ get_SVG_UI_Icon("multiply") }; background-position:center; background-size:12px; justify-content:center; opacity:0.55; }
#close_playlist_btn_container, #close_playlist_btn { display:none; flex-basis:24px; }
#parents_dir_nav { line-height:1.4; }
#parents_links { right:0; left:0; box-shadow:0px 4px 6px -3px #333; z-index:9998; }
#parents_links a { padding:4px 8px; }
#current_dir_path { padding:2px 6px 4px; hyphens:none; word-break:break-word; z-index:9998; }
/* MAIN MENU */
#menu { right:0;left:0; zindex:9998; box-shadow:0px 4px 6px -3px #333; z-index:9998; }
#menu li { display:flex; }
#menu li.has_submenu { position:relative; justify-content:space-between; }
#menu li.bookmark a::before { content:""; width:24px; max-width:24px; min-width:24px; height:12px; background-size:12px; }
.submenu { width:100%; max-width:240px; display:none; margin:0; padding:0; position:absolute; top:-1px !important; left:100%; right:0; box-shadow:0px 4px 6px -3px #333; }
#menu ul.submenu li a { padding:6px 8px 6px 0; }
#menu input { width:0; float:left; }
.menu_item { margin:0; padding:5px 8px 5px 0; display:flex; flex-grow:1; }
#menu .selected ~ li:hover .submenu, #menu .selected ~ li .submenu:hover, .has_open { display:none; }
#menu svg { margin: 0 6px; width:12px; }
/* IFRAME MENUS */
#parent { padding:5px 3px 5px 0; }
#parent span { padding:0px 1px; }
#open_in_sidebar { padding:5px 2px 5px 3px; }
`;
const directory_header_styles = ` /* for both sidebar and content_iframe */
#directory_wrapper { z-index:1; }
#directory_wrapper.top { min-width:220px; }
#directory_wrapper.iframe { min-width:500px; flex-basis:100%; }
#directory_header { font-size:var(--font_size_small); z-index:3; user-select:none; -webkit-user-select:none; }
.directory_title_div { letter-spacing:0.5em; text-indent:0.75em; flex-basis:100%; }
.directory_title_div:before { content:"INDEX OF"; }
.has_playlist .directory_title_div:before { content:"PLAYLIST"; }
.has_filelist .directory_title_div:before { content:"FILELIST"; }
${ directory_header_menu_styles }
/* SIDEBAR BUTTONS */
#directory_buttons_left { padding:6px; }
#show_details { margin-top:0; margin-right:0.5em; padding:0 4px 0 4px; }
#show::before { content:"Show "; }
#show_invisibles { margin:0 4px 0 0; }
/* GRID BTN ---> combine style with save_btn */
#show_grid { margin:0 0 0 auto; }
#show_grid ul { padding-left:0px; top:-1px; right:-1px; box-shadow:0px 4px 6px -3px #333; }
#show_grid.has_grid div { color:#118888; }
#top.has_images #show_grid, #top.has_fonts #show_grid { display:flex; }
.has_images.has_fonts #show_grid:hover ul.menu { display:grid; }
/* SORTING ROW */
#sorting .sorting { grid-row:1; white-space:pre; cursor:pointer; }
.show_details #sorting_row_2 { display:grid; }
#sorting_row_2.iframe { grid-template-columns:minmax(14em,100%) minmax(6em,8em) minmax(4em,8em) minmax(4em,14em) minmax(4em,8em); }
#sorting_row_1 span, #sorting_row_2 span, .sorting span::before, .sorting span::after
{ display:inline-block; }
#sorting_row_1 span, #sorting_row_2 span { padding:6px 0; }
#sorting_row_1.iframe span, #sorting_row_2.iframe span { padding:4px 0; }
#sorting span::before, #sorting span::after, .sorting .menu_item::after { content:""; width:16px; height:8px; color:#CCC; background-position:center; background-repeat:no-repeat; background-size:10px; }
.sorting.down span::after, .sort_descending .sorting span::after { transform:rotate(0deg) !important; }
#sort_by_name input { margin:-2px 6px -2px 0; bottom:-2px; }
#sort_by_ext { grid-column: span 2; }
.has_media #sort_by_ext { grid-column: span 1; }
.has_media #sort_by_default { text-align:center; }
#sort_by_default.iframe, .iframe #sort_by_size, .iframe #sort_by_date { text-align:right; }
.has_media #sort_by_duration, .has_playlist #sort_by_duration { display:unset; }
/* TEXT EDITOR ROW */
.show_details #show_text_editor, #show_text_editor.has_text_editor { display:flex; }
#show_text_editor a { padding:6px 0 6px 16px; }
`;
const directory_nav_styles = ` /* for both sidebar and content_iframe */
.directory_item_name_a::before, .directory_item_input, .directory_item_details, .directory_item_details span, .directory_item_media_duration, .details.ext, .directory_item.error::before, .hide_ignored_items:not(.has_stats) #directory_list li.ignored, .hide_ignored_items:not(.has_stats) .directory_item.ignored, .hide_ignored_items:not(.show_invisibles) .directory_item.ignored.invisible, body:not(.show_invisibles) .directory_item.invisible
{ display:none; }
#directory_nav { overflow-y:hidden; flex-basis:100%; font-size:var(--font_size_small); }
#directory_nav_inner { overflow:auto; margin-bottom:-1px; }
#directory_nav ol { -webkit-margin-before:0em !important; -webkit-margin-after:0em !important; -webkit-padding-start:0em; }
#directory_list { height:100%; counter-reset:item; transition:opacity .125s; }
#directory_list:empty { padding:100%; }
.directory_item { margin-inline-start:0; display:grid; grid-gap:0; font-variant-numeric:tabular-nums; }
.top_item { grid-template-columns:minmax(8rem,auto) minmax(6em,1fr) minmax(auto,6em); }
.directory_item_name { grid-row:1; display:flex; -webkit-padding-start:0; -moz-padding-start:0; word-break:break-word; }
.top_item .directory_item_name { grid-column:1 / span 3; padding:6px 12px 6px 0; }
.top_item.media .directory_item_name { grid-column:1 / span 2; padding-right:0; }
.directory_item_name::before { counter-increment:item; content:counter(item); width:36px; min-width:36px; height:14px; max-height:14px; min-height:14px; text-align:right; padding:0; }
.directory_item_input { margin:1px 6px 0 0; }
.has_icon_before { display:inline-block; }
.has_icon_before::before, .has_icon_before_before { content:""; display:inline-block; max-width:28px; width:28px; min-width:28px; height:20px; margin-top:-3px; margin-bottom:-6px; background-position:center; background-repeat:no-repeat; background-size:14px,0px; }
.directory_item.image { position:relative; }
body.show_image_thumbnails:not(.has_grid) .directory_item.image .has_icon_before_before:hover
{ position:absolute; width:72px; max-width:unset; height:72px; max-height:unset; background-size:contain; background-color:hsl(0,0%,var(--percent_95)); z-index:2; top:8px; left:14px; }
body.show_image_thumbnails:not(.has_grid) .directory_item.image .has_icon_before_before:hover + .name_span { padding-left:28px; }
.directory_item.media .directory_item_media_duration { grid-row:1; }
.top_item.media .directory_item_media_duration { grid-column:3; padding:6px 12px 6px 0; }
.media:not(.local) .directory_item_media_duration { display:unset; }
.sort_by_default.show_invisibles .dir.invisible + .dir:not(.invisible), .sort_by_default.show_invisibles .dir:not(.invisible) + .dir.invisible { border-top:solid 1px hsl(0,0%,var(--border_lum)); }
.directory_item_details { text-align:right; white-space:nowrap; }
.top_item .directory_item_details { padding:0 12px 4px 0; }
.directory_item_details.size { padding-left:12px; }
.directory_item_details.date { padding-bottom:0; height:1em; max-height:1em; overflow:hidden; overflow-wrap:break-word; }
.directory_item_details.kind::first-letter { text-transform:uppercase; }
#iframe_body .iframe_item { grid-template-columns: minmax(20em,100%) minmax(4em,6em) minmax(6em,8em) minmax(6em,14em) minmax(6em,8em); }
#iframe_body .iframe_item.media .directory_item_media_duration { grid-column:2; }
#iframe_body .iframe_item.not_media .directory_item_name { grid-column:1 / span 2; }
#iframe_body .iframe_item .directory_item_details { grid-row:1; height:1ex; }
#iframe_body .iframe_item .directory_item_name_a, #iframe_body .iframe_item > span { padding:5px 16px 5px 0; }
.directory_item:hover a, .selected a, .media[class*="_loaded"] a { opacity:1 !important; font-weight:bold; }
.show_numbers .directory_item_name_a::before { display:initial; }
.show_details .directory_item_details, .media .directory_item_input { display:unset; }
.disabled, .ignore_ignored_items li.ignored, .has_filelist [id$="sort_by_size"], .has_playlist [id$="sort_by_size"], .has_filelist [id$="sort_by_date"], .has_playlist [id$="sort_by_date"]
{ cursor:not-allowed; opacity:0.66; }
.directory_item.error { display:block; padding:6px 8px; }
.show_invisibles .directory_item.invisible { display:grid; }
.directory_item.ignored.local .directory_item_name_a::after { content:"\\00a0[local file]"; display:contents; font-style:italic; }
body.show_invisibles .invisible, .directory_item:not(.ignored):not(.invisible), body.show_invisibles .directory_item.invisible, body.has_stats .directory_item.invisible, body.has_stats .directory_item.ignored, body.has_stats .directory_item.ignored.hovered, body.hide_ignored_items.has_stats .directory_item.ignored, body.hide_ignored_items.has_stats .directory_item.ignored.invisible, body:not(.hide_ignored_items) .directory_item.ignored:not(.invisible) { display:grid; }
.tr[data-level] .directory_item_name_a::before { width:${ ( ( Number(getSearchParam("level") ) + 1) * 22 ) + 28 }px; }
${ CSS_UI_Icon_Rules() } /* background icons */
.is_error #is_error { display:block !important; grid:none !important; grid-template-columns:none !important; }
.is_error #is_error_items { display:block; padding:6px 8px; }
`;
const iframe_dir_styles = `
${ global_styles }
#iframe_body { overflow-x:auto; font-size:${ (parseFloat($settings.UI_font_size) * 0.875) + $settings.UI_font_size.replace(/\d*/,'') }; }
.theme_dark .sorting span::before, .theme_dark .sorting span::after { filter:invert(1); }
#iframe_body.is_blurred #directory_list_outer { opacity:0.75; }
.show_details #show::before { content:"Hide "; }
`;
const directory_footer_styles = ` /* for both sidebar and content_iframe */
.has_stats #stats_summary, .stats_kind span.file, .stats_kind span.media { display:none; }
#directory_footer { font-variant-numeric:tabular-nums; user-select:none; -webkit-user-select:none; font-size:var(--font_size_small); }
#stats_container { width:100%; max-height:${ (window.clientHeight * 0.33) }px; }
#stats { font-size:var(--font_size_small); overflow:hidden; }
.theme_light #footer_utilities:hover ul, .theme_light #directory_footer:hover, .theme_light #stats_summary_detailed { box-shadow:0px -4px 4px 0px rgba(128,128,128,0.6); }
.theme_dark #footer_utilities:hover ul, .theme_dark #directory_footer:hover, .theme_dark #stats_summary_detailed { box-shadow:0px -4px 4px 0px rgba(32,32,32,0.6); }
#stats_summary_detailed_dirs .stats_kind::before { background-image:${ get_SVG_UI_File_Icon("file_icon_dir") }; }
#stats_summary_detailed_files .stats_kind::before { background-image:${ get_SVG_UI_File_Icon("file_icon_file_default") }; }
#stats_summary_detailed, #stats_details_container { overflow-y:scroll; }
.stats_list_item { line-height:1.2; }
#stats_summary_totals, .has_media #total_duration { display:flex; text-align:left; font-weight:normal; white-space:normal; padding-right:1em; }
.has_media #total_duration::before { content:"Total Time:\\00a0"; }
#stats a { padding:4px 12px 4px 0; }
#stats_summary_detailed_total li, #stats_summary_playlist_container { padding:5px 8px; overflow:hidden; white-space:pre; }
#stats a::before { content:attr(data-count); width:36px; text-align:right; }
.stats_kind span { margin-right:0.5em; white-space:pre-wrap; display:inline-block; }
.stats_kind > span::first-letter { text-transform:uppercase; }
#stats_details span.ignored::before,#stats_details span.invisible::before { content:"("; }
#stats_details span.ignored::after,#stats_details span.invisible::after { content:")"; }
#stats_details li.audio a span span::after { content:attr(data-audio_duration); white-space:pre; }
#stats_details li.video a span span::after { content:attr(data-video_duration); white-space:pre; }
#total_duration::after { content:attr(data-time_remaining); white-space:pre; }
/* IFRAME STATS: */
.stats_list_item_name_a { -webkit-padding-start:0; padding:1px 0; }
#stats_summary, #stats_summary_detailed { margin-block-start:0; margin-block-end:0; }
#stats_summary li, #stats_summary_detailed_total, #stats_summary_playlist_container { padding:4px 8px; }
.stats_list_item_name_a_span, #stats .stats_kind span { margin-right:0.5em; white-space:pre; display:inline-block; }
#stats a.directory_item_name_a:before { display:inline-block; }
#stats_details { max-height:25vh; }
#stats_container, .has_stats #stats_summary_detailed, .has_stats #stats_details { display:block; }
/* SIDEBAR FOOTER LINKS */
#footer_utilities { z-index:1; right:-1px; bottom:-1px; }
#footer_utilities ul { position:absolute; bottom:0; right:0; white-space:nowrap; box-shadow:-0px -3px 6px -3px #333; }
#footer_utilities:hover ul { display:block; }
.has_stats #footer_utilities { display:none; }
`;
const directory_utilities_styles = `
#hide_sidebar { top:0; right:0; height:21px; z-index:9997; cursor:pointer; opacity:0.6; }
#hide_sidebar:hover { opacity:1; }
/* HAS HIDDEN SIDEBAR */
.hide_sidebar #handle { display:none; }
.hide_sidebar #hide_sidebar { left:2px; transform:rotate(180deg); }
.hide_sidebar #directory_wrapper { width:0 !important; min-width:0; position:absolute; top:2px; left:-1px; }
.hide_sidebar #directory_header { z-index:unset; display:none; }
.hide_sidebar #directory_nav { visibility:hidden; } /* allows hidden sidebar to be navigated by arrows */
.hide_sidebar #directory_list_outer { min-width:0; }
.hide_sidebar #content_pane { width:100% !important; }
.hide_sidebar #title_buttons_left { padding-left:24px; }
#handle { top:0; bottom:0; z-index:1; right:-4px; width:7px; cursor:col-resize; }
.has_overlay #handle { z-index:9999; }
`;
const sidebar_styles = `${ directory_header_styles } ${ directory_nav_styles } ${ directory_footer_styles } ${ directory_utilities_styles }`;
/* CONTENT PANE STYLES */
const content_pane_header_styles = `
#content_header { font-size:var(--font_size_small); z-index:3; }
/***** CONTENT TITLE *****/
#content_title_container { overflow-x:scroll; }
#content_title { line-height:1.4; min-width:16em; min-height:18px; padding:4px 8px; text-align:center; word-break:break-word; vertical-align:top; }
#content_title span { font-weight:bold; hyphens:none; }
#content_title span::before { font-weight:normal; }
#content_pane[data-content="has_font"] #content_title::before { content:"Font:" }
#content_pane[data-content="has_font_file"] #content_title::before { content:"Glyphs from font:" }
.has_directory_source #content_title::before { content:"Source of:" }
#content_pane[data-content="has_grid"] #content_title::before { content:"Fonts and Images from:"; }
#content_pane[data-content="has_grid"].has_font_grid #content_title::before { content:"Fonts from:"; }
#content_pane[data-content="has_grid"].has_image_grid #content_title::before { content:"Images from:"; }
#content_pane[data-content="has_ignored"] #content_title::before { content:"Ignored content:"; }
#content_pane[data-content="has_dir"] #content_title::before { content:"Index of:"; }
#content_pane[data-content="has_grid"] #content_title::after { content:"${ current_dir }"; }
.text_editor_raw:not(.text_editor_split_view) #content_pane[data-content="has_text_editor"] #content_title::after { content:" (Source Text)"; }
.text_editor_preview:not(.text_editor_split_view) #content_pane[data-content="has_text_editor"] #content_title::after { content:" (Text Preview)"; }
.text_editor_html:not(.text_editor_split_view) #content_pane[data-content="has_text_editor"] #content_title::after { content:" (HTML Preview)"; }
#content_pane[data-content="has_text_editor"] #content_title span::before { background-image:${ get_SVG_UI_File_Icon("file_icon_markdown") }; }
#content_pane[data-content="has_font_file"] #content_title span::before { background-image:${ get_SVG_UI_File_Icon("file_icon_font") }; }
#content_pane[data-content="has_grid"] #content_title span::before { background-image:${ get_SVG_UI_File_Icon("file_icon_dir") }; height:14px !important; }
#content_pane[data-content="has_view_directory_source"] #content_title span::before { background-image:${ get_SVG_UI_File_Icon("file_icon_dir_default") }; height:14px !important; background-size:contain; }
.is_error #content_title span::before { content:"ERROR"; white-space:pre; }
#content_pane[data-content="has_image"] #content_title span::after { content:attr(data-after); font-weight:normal; }
#content_pane[data-content="has_text_editor"] #content_title span::after { content:"Text Editor"; font-weight:bold; }
#content_pane[data-content="has_text_editor"].edited #content_title span::after { content:"Text Editor (edited)"; font-weight:bold; }
#content_pane[data-loaded="unloaded"] #content_title::before { content:"Loading..." }
#content_pane[data-loaded="unloaded"] #content_title span { display:none; }
/* CONTENT TITLE BUTTONS LEFT */
#reload_btn { width:52px; }
#reload_btn::before { content:"Reload"; }
#prev_next_btns { margin-left:4px; }
#prev_next_btns span { width:2em; height:16px; }
#prev_next_btns span:active { background-color:#0E4399; }
#prev_next_btns:focus { background-color:white; }
#prev_next_btns svg { width:12px; }
/* CONTENT TITLE BUTTONS RIGHT */
#scale { margin-right:4px; background-color:#FFF; }
#scale span { width:2em; }
#close_btn { width:52px; }
#close_btn::before { content:"Close"; }
#content_pane[data-content="has_text_editor"] #close_btn::before { content:"Hide"; }
.split_btn span { display:inline-flex; }
.split_btn::after { content:""; position:absolute; top:0; bottom:0; left:50%; border-left:solid 1px #333; }
#open_in_text_editor { margin-right:4px; }
`;
const content_pane_audio_styles = `
/* CONTENT AUDIO TITLE */
#content_audio_title span { width:100%; padding:4px 6px 0; text-align:center; line-height:1.4; cursor:pointer; }
#content_audio_title span::before { content:""; padding-right:22px; height:14px !important; font-weight:normal; background-image:${ get_SVG_UI_File_Icon("file_icon_audio") }; background-position:center; background-position:right 4px center; background-repeat:no-repeat; }
#content_pane.has_audio #content_audio_title span::before, #content_pane[data-content="has_video"] #content_title::before { content:"Playing:"; }
/* CONTENT AUDIO PLAYER */
#content_audio { justify-content:center; padding:2px 6px 6px; overflow-x:auto; }
#audio_container { height:32px; background-color:rgb(241, 243, 244); }
#prev_track, #next_track { width:2rem; }
#audio { height:32px; }
audio::-webkit-media-controls-enclosure { border-radius:0; }
#close_audio { width:32px; }
#audio_options { margin-top:0; margin-right:calc(-6em - 8px); padding:0 4px; width:6em; justify-content:start; }
#loop_label input { margin:0px 4px 2px}
#shuffle_label input { margin:2px 4px 0px}
/* CUE SHEET MENU */
.cue_sheet_track_list_container { width:32px; background-image:${ get_SVG_UI_File_Icon("file_icon_playlist") }; background-position:center; background-repeat:no-repeat; background-size:18px; background-blend-mode:luminosity; background-color:inherit; display:none; }
.cue_sheet_track_list { }
#cue_sheet_track_list_container_audio:hover > div { padding-top:13px; display:flex; flex-direction:column; position:absolute; left:0; right:0; margin-top:-1px; z-index:1; overflow:auto; font-size:0.875em; }
.cue_sheet_track { display:flex; justify-content:space-between; }
.cue_sheet_track.selected .cue_track_id::before { content:""; width:16px; height:8px; display:flex; }
.cue_sheet_track span { padding:4px 8px; font-variant-numeric:tabular-nums; }
#cue_sheet_title { padding:4px 8px; font-variant-numeric:tabular-nums; text-align:center; }
/* CONTENT TITLE PLAYLIST ENTRY (#content_playlist and #content_audio_playlist) */
.playlist_entry_container { flex-direction:row; }
.playlist_entry_container textarea { padding:0 6px; width:100%; border:0; resize:vertical; }
#content_pane.has_audio #audio_wrapper, .playlist_entry_container.has_content { display:flex; flex-direction:column; }
audio::-webkit-media-controls-panel { padding:0; }
`;
const content_pane_styles = `
#content_pane { padding:0; height:100%; transform:scale(1); vertical-align:top; contain:strict; }
${ content_pane_header_styles }
${ content_pane_audio_styles }
/***** CONTENT_CONTAINER *****/
#content_container { justify-content:center; bottom:0; background-position:center; background-repeat:no-repeat; background-size:33.33%; contain:strict; flex-basis:100%; overflow:auto; }
.content { width:100%; height:100%; margin:0; padding:0; overflow:auto; display:none; }
#content_pane[data-loaded="unloaded"] #content_container > svg { display:block; }
#content_pane[data-loaded="unloaded"] .content { display:none !important; }
/* CONTENT GRID (div) */
#content_grid { width:100%; font-size:1rem; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(${ ( $settings.grid_image_size + 16) }px, auto)); grid-auto-rows:minmax(min-content, max-content); }
#content_pane.has_hidden_grid #content_grid { max-height:100%; overflow:hidden; visibility:hidden; }
#content_grid::after { content:""; width:1px; position:absolute; top:0; right:0; bottom:0; }
#content_pane[data-content="has_grid"] #content_grid { display:grid; }
/* IMAGE GRID ITEMS */
.image_grid_item { padding:6px; grid-column:auto; line-height:0; }
.image_grid_item img { width:auto; max-width:${ ($settings.grid_image_size).toString() }px; max-height:${ ($settings.grid_image_size) }px; position:relative; }
.image_grid_item img[src$=".svg"] { height:100%; }
.image_grid_item.selected { box-shadow:inset 0px 0px 4px hsl(0,0%,var(--percent_60)); }
/* FONT GRID ITEMS */
.font_grid_item { line-height:1; padding:8px 20px; grid-column:1 / -1; }
.font_grid_item p { padding:0 0 6px 0; line-height:1; font-size:1rem; letter-spacing:0.1em; text-indent:0.1em; }
.font_grid_item h2 { font-size:${ $settings.grid_font_size * 4 }em; font-weight:normal; }
.image_grid_item + .font_grid_item { margin-top:-1px; border-top:solid 1px hsl(0,0%,var(--border_lum_inverted)); }
/* CONTENT FONT.content */
#content_font { hyphens:none; font-size:${ $settings.grid_font_size }em; overflow-wrap:break-word; flex-direction:column; }
#content_pane[data-content="has_font"] #content_container { overflow:hidden; }
#content_pane[data-content="has_font"] #font_toolbar { display:flex; flex-direction:row; left:0; right:0; padding:2px 4px; overflow-x:scroll; overflow-y:hidden; -webkit-user-select:none;-moz-user-select:none; user-select:none; z-index:3; }
#font_toolbar li { padding:0 4px; white-space:pre; }
#font_toolbar li.text { width:50%; font-size:var(--font_size_small); }
#font_tag_textarea { resize:none; }
#font_specimen { max-width:100%; padding:40px 20px 20px; flex-direction:column; overflow-x:hidden; }
#font_specimen .specimen { margin:6px 0; padding: 0 8px 12px; overflow:visible; font-weight:normal; line-height:1.2; outline:none; }
#font_specimen .specimen:focus,#font_specimen .specimen:focus-visible { box-shadow:inset 0 0 2px 2px hsl(212deg 50% 60%); border-radius:3px; }
#font_specimen hr { width:100%; margin:0; }
#specimen { font-size:4em; font-weight:normal;}
#font_specimen_grid { display:grid; font-size:4em; hyphens:auto; margin:0 0 20px; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(1.5em, auto)); grid-auto-rows:minmax(min-content, max-content); overflow:visible; }
#font_specimen_grid:empty, #specimen_glyph:empty, #font_specimen_grid:empty + hr { display:none; }
#font_specimen_grid div { grid-column:auto; padding:6px; display:flex; justify-content:center; position:relative; cursor:pointer; }
#font_specimen_grid div::before, #font_toolbar::before { content:attr(data-char); position:absolute; font-size:0.75rem; bottom:0; left:2px; font-family:${$settings.UI_font}; opacity:0.66; font-feature-settings:normal; font-variant:normal; }
#font_specimen_grid div::after, #font_toolbar::after { content:attr(data-unicode_hex); position:absolute; font-size:0.75rem; bottom:0; right:2px; font-family:${$settings.UI_font}; opacity:0.66; font-feature-settings:normal; font-variant:normal; }
#font_toolbar::before, #font_toolbar::after { display:block; padding:4px 6px; bottom:calc(-9px - 1em); font-size:0.875em; opacity:1; z-index:2; }
#specimen_2, #specimen_3 { padding:0.125em 0; line-height:1; text-overflow:ellipsis; white-space:pre; overflow:clip; overflow-clip-margin:20em; }
#specimen_2 { font-size:8em; }
#specimen_3 { font-size:6em; }
#specimen_2H4 { font-size:1.618em; }
#specimen_3H3 { font-size:2em; hyphens:auto; }
#specimen_string_2, #specimen_string_3 { padding:0 0 20px; text-align:justify; hyphens:auto; }
.lorem { text-align:justify; hyphens:auto; font-size:1em; column-gap:1.5em; overflow-wrap:normal; }
#lorem::first-line { letter-spacing:0.1em; text-indent:0.1em; font-size:${ $settings.grid_font_size * 1.33 }em; font-variant:small-caps; }
#lorem_2 { padding:12px 0 0; columns:2; }
#lorem_3 { padding:12px 0 0; columns:3; }
#specimen_glyph { z-index:2; }
#specimen_glyph_overlay { z-index:1; }
#specimen_glyph, #specimen_glyph:not(:empty) + #specimen_glyph_overlay { top:0; left:0; right:0; bottom:0; display:flex; justify-content:center; font-size:64vw; overflow:visible; user-select:none; }
/* FONT GLYPHS */
#font_file_viewer { font-family:unset; width:100%; }
#content_pane[data-content="has_font_file"] #font_file_viewer, #content_pane[data-content="has_glyph"] #font_file_viewer { overflow:auto; }
#content_pane[data-content="has_glyph"] #font_file_grid { visibility:hidden; }
#font_file_grid { margin-bottom:21px; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(120px,auto)); }
#glyph_container { width:100%; justify-content:center; }
.glyph_container:hover, .glyph_container.selected { z-index:1; }
#glyph_container .glyph_container, .glyph_container:hover svg, .glyph_container.selected svg, #svg_container, #svg_container svg { overflow:visible; }
#svg_container svg { width:50%; }
.glyph_info { padding:2px; position:absolute; right:0; bottom:0; left:0; font-size:0.75rem; justify-content:space-between; }
#glyph_viewer { z-index:1; top:0; right:0; bottom:0; left:0; background-color:#FFF; background-position:center; background-repeat:no-repeat; background-size:contain; }
#glyph_viewer_info { position:fixed; z-index:1; right:0; left:0; text-align:center; height:18px; line-height:1.6; display:flex; font-size:var(--font_size_small); }
#glyph_viewer_info div { padding:0; }
#glyph_viewer svg path, #glyph_viewer svg g { transform-origin:center; }
#save_svg_hidden { float:left; visibility:hidden; }
#glyph_viewer_info div::before { content:"Glyph "; }
#save_svg { float:right; }
#font_info { margin:0; max-height:${ (window.innerHeight * 0.75) }px; left:-1px; right:0; bottom:-1px; z-index:2; font-size:var(--font_size_small); overflow-y:auto; }
#font_info:hover { box-shadow:0px 4px 6px 3px #333; }
#content_pane[data-content="has_font_file"] #font_info { display:block; }
/* CONTENT TEXT EDITOR */
#content_text { width:100%; max-width:100%; height:100%; display:none; flex-direction:column; overflow:hidden; z-index:1; }
/* OTHER CONTENT ELEMENTS */
#content_image_container { padding:2rem 2.5rem; box-sizing:border-box; }
.has_zoom_image #content_image_container, .has_scaled_image #content_image_container { padding:0; }
#content_image { margin:auto; width:auto; max-width:100%; max-height:100%; object-fit:contain; cursor:zoom-in; }
#content_image:focus-visible { outline:none; }
#content_pane[data-content="has_image"] #content_image_container { display:flex; }
#content_pane.has_scaled_image #content_image_container { display:grid; }
#content_pane.has_zoom_image #content_image { width:unset; max-width:unset; max-height:unset; cursor:zoom-out; }
#content_video { background:transparent; }
#content_iframe { background:white; border:0; }
`;
const main_styles = `
/* WIDTHS & HEIGHTS */
#content_font svg, .image_grid_item img[src$=".svg"] { width:100%; }
/* DISPLAY */
${ global_styles }
.cue_sheet_track_list_container:hover .cue_sheet_track_list, #content_grid a { display:block; }
/* TEXT STYLES */
li.has_submenu:hover > span, .menu_item:hover, #directory_menus li.selected > span, #show_invisibles:hover span, .directory_item:hover a, #directory_wrapper .selected a, .media[class*="_loaded"] a, #stats_summary_detailed tr:hover, #stats_details tr:hover, .cue_sheet_track:not(.header):hover, .cue_sheet_track.selected
{ font-weight:bold; }
button { padding:2px 6px; background-color:hsl(0,0%,95%); border:solid 1px #333; border-radius:3px; height:18px; font-size:0.875em; font-family:${$settings.UI_font} !important; cursor:pointer; }
button.focus, button:focus { outline:none; border-radius:3px !important; border-style:solid !important; border-width:1px !important; border-color:#222 !important; }
textarea:focus, audio:focus { outline:none; }
/* VARIOUS CLASSES AND ELEMENTS */
.ignored .has_icon_before::before, .ignored .has_icon_before_before { filter:grayscale(100%); }
/* combine with has_flyout_menu or add background color classes to elements: */
.has_popout_menu, .text_editor_theme_light #toolbar .has_popout_menu { background-color:#C0C0C0; border:solid 1px #666; }
.has_popout_menu li { background-color:#D0D0D0; }
.has_popout_menu li:hover { background-color:#E0E0E0; }
.theme_dark #directory_wrapper .has_popout_menu { border:solid 1px #111; }
.theme_dark #directory_wrapper .has_popout_menu, .theme_dark #directory_wrapper .has_popout_menu li { background-color:#505050; }
.text_editor_theme_dark #toolbar .has_popout_menu li { background-color:#C0C0C0; }
.theme_dark #directory_wrapper .has_popout_menu li:hover { background-color:#686868; }
/***** APPEND STYLES *****/
${ sidebar_styles } ${ content_pane_styles } ${ utilities_styles }
`;
const background_images = `
/* BACKGROUND IMAGES */
.has_background, .has_background_before::before, .has_background_after::after { background-repeat:no-repeat; background-position:center; background-color:transparent !important; }
.bookmark > a::before { background-image:${ get_SVG_UI_Icon("bookmark") }; }
.menu_item::before { content:""; width:24px; max-width:24px; min-width:24px; height:9px; margin:2px 0 -2px; background-position:center; background-repeat:no-repeat; display:flex; }
.background_color_check_mark::before, .sort_by_default #menu_sort_by_default .menu_item::before, .sort_by_name #menu_sort_by_name .menu_item::before, .sort_by_duration #menu_sort_by_duration .menu_item::before, .sort_by_size #menu_sort_by_size .menu_item::before, .sort_by_date #menu_sort_by_date .menu_item::before, .sort_by_kind #menu_sort_by_kind .menu_item::before, .sort_by_ext #menu_sort_by_ext .menu_item::before, #menu_theme_container .menu_item::before, .alternate_background #alternate_background .menu_item::before, .show_image_thumbnails #show_image_thumbnails .menu_item::before, .show_numbers #show_numbers .menu_item::before, .autoload_media #autoload_media .menu_item::before, .autoload_index_files #autoload_index_files .menu_item::before, .play_all_media #play_all_media .menu_item::before, #toggle_text_editing .menu_item::before, .text_editor_split_view #text_editor_split_view_menu_item, .text_editor_split_view #text_editor_split_view span::before, .text_editor_raw #text_editor_raw::before, .text_editor_preview #text_editor_preview::before, .sort_by_default #sort_by_default span::before, .loop_media #loop_media_menu::before, .shuffle_media #shuffle_media_menu::before, .sort_by_name #sort_by_name span::before, .sort_by_duration #sort_by_duration span::before, .sort_by_size #sort_by_size span::before, .sort_by_date #sort_by_date span::before, .sort_by_kind #sort_by_kind span::before, .sort_by_ext #sort_by_ext span::before, .alternate_background #alternate_background span::before, .hide_ignored_items #hide_ignored_items span::before, .ignore_ignored_items #ignore_ignored_items span::before, .text_editor_theme_default #text_editor_theme_default::before, .text_editor_theme_light #text_editor_theme_light::before, .text_editor_theme_dark #text_editor_theme_dark::before, #enable_text_editing::before, .text_editor_raw #text_editor_raw::before, .text_editor_preview #text_editor_preview::before, .text_editor_html #text_editor_html::before, .cue_sheet_track.selected .cue_track_id::before
{ background-image:${ get_SVG_UI_Icon("check_mark") }; }
.sort_by_default #sort_by_default span::after, .sort_by_name #sort_by_name span::after, .sort_by_duration #sort_by_duration span::after, .sort_by_size #sort_by_size span::after, .sort_by_date #sort_by_date span::after, .sort_by_kind #sort_by_kind span::after, .sort_by_ext #sort_by_ext span::after { background-image:${ get_SVG_UI_Icon("chevron") }; background-size:75%; transform:rotate(180deg); }
.is_error #content_container { background-image:${ get_SVG_UI_Icon("error") }; }
#content_pane[data-content="has_ignored"] #content_container { background-image:${ get_SVG_UI_File_Icon('file_icon_ignored') }; background-size:28px; }
.has_audio #content_pane[data-content="has_null"]:not([data-loaded="unloaded"]) #content_container
{ background-image:${ get_SVG_UI_Icon("music") }; }
${ CSS_UI_Icon_Rules() }
#current_dir_path span::before { background-image:${ get_SVG_UI_File_Icon("file_icon_dir") }; display:inline-block; margin:0 0 -5px 0; width:24px; }
.has_playlist #current_dir_path span::before { background-image:${ get_SVG_UI_File_Icon("file_icon_playlist") }; display:inline-flex; margin:0 0 -2px 0; width:24px; }
.is_error #current_dir_path span::before { background-image:${ get_SVG_UI_Icon("error") }; float:none; display:inline-flex; margin:0 0 -2px 0; width:24px; }
.dir_list_subdir_loading .has_icon_before_before { background-image:${ get_SVG_UI_Icon("spinner") } !important; background-size:20px; }
`;
const color_and_background_styles = `/* added to #top and #iframe_body */
:root { color-scheme:none; }
.theme_light, .text_editor_theme_light #content_text, #iframe_body.has_warning.text_editor_theme_light {
--percent_100:100%; --percent_95:95%; --percent_90:90%; --percent_85:85%; --percent_80:80%; --percent_75:75%; --percent_70:70%; --percent_65:65%; --percent_60:60%; --percent_55:55%;
--percent_50:50%; --percent_45:45%; --percent_40:40%; --percent_35:35%; --percent_30:30%; --percent_25:25%; --percent_20:20%; --percent_15:15%; --percent_10:10%; --percent_05:05%; --percent_00:00%;
--non_media_background_h:210deg; --non_media_background_s:61%; --non_media_background_l:80%;
--media_background_h:180deg; --media_background_s:36%; --media_background_l:64%;
--text_editor_item_background_h:240deg; --text_editor_item_background_s:60%; --text_editor_item_background_l:75%;
--border_lum:40%; --border_lum_inverted:40%;
}
.theme_dark, .text_editor_theme_dark #content_text, #iframe_body.has_warning.text_editor_theme_dark {
--percent_100:00%; --percent_95:05%; --percent_90:10%; --percent_85:15%; --percent_80:20%; --percent_75:25%; --percent_70:30%; --percent_65:35%; --percent_60:40%; --percent_55:45%;
--percent_50:50%; --percent_45:55%; --percent_40:60%; --percent_35:65%; --percent_30:70%; --percent_25:75%; --percent_20:80%; --percent_15:85%; --percent_10:90%; --percent_05:95%; --percent_00:100%;
--non_media_background_h:210deg; --non_media_background_s:34%; --non_media_background_l:55%;
--media_background_h:180deg; --media_background_s:31%; --media_background_l:43%;
--text_editor_item_background_h:240deg; --text_editor_item_background_s:25%; --text_editor_item_background_l:50%;
--border_lum:05%; --border_lum_inverted:40%;
}
${ background_images }
/* BACKGROUND COLORS */
.background_grey_60 { background-color:hsl(0,0%,var(--percent_60)); }
.background_grey_65 { background-color:hsl(0,0%,var(--percent_65)); }
.background_grey_70 { background-color:hsl(0,0%,var(--percent_70)); }
.background_grey_75 { background-color:hsl(0,0%,var(--percent_75)); }
body, .menu li:hover, .menu span:hover
{ background-color:hsl(0,0%,var(--percent_75)); }
.background_grey_80, .alternate_background .directory_item:nth-of-type(even), .alternate_background .cue_sheet_track:not(.header):nth-of-type(even)
{ background-color:hsl(0,0%,var(--percent_80)); }
.background_grey_85, .alternate_background .directory_item:nth-of-type(odd), .alternate_background .cue_sheet_track:not(.header):nth-of-type(odd), #utilities_help li:nth-of-type(even)
{ background-color:hsl(0,0%,var(--percent_85)); }
.background_grey_90 { background-color:hsl(0,0%,var(--percent_90)); }
.background_grey_95, .background_grey_90:hover, .background_grey_90.hovered, .background_grey_90.selected, .background_grey_90:focus, #content_grid::after
{ background-color:hsl(0,0%,var(--percent_95)); }
.background_grey_100, .background_grey_95:hover, .background_grey_95.hovered, .background_grey_95.selected
{ background-color:hsl(0,0%,var(--percent_100)); }
/* NON-MEDIA ITEMS .selected, .loaded, :hover ("light cyan") */
body:not(.has_stats) .not_media:not(#is_error).selected, body:not(.has_stats) .directory_item.content_loaded:not(video), body:not(.has_stats) .directory_item.content_loaded:not(.video):hover, #menu li.selected
{ background-color:hsla(var(--non_media_background_h),var(--non_media_background_s),var(--non_media_background_l),1.00) !important; }
body:not(.has_stats) .directory_item.content_loaded:not(.video), body:not(.has_stats) .not_media:not(#is_error):hover, body.alternate_background:not(.has_stats) .not_media:not(#is_error):hover, .not_media:not(#is_error).hovered { background-color:hsla(var(--non_media_background_h),var(--non_media_background_s),var(--non_media_background_l),0.80) !important; }
/* MEDIA ITEMS .audio_loaded, .video.content_loaded, .selected, :hover */
body:not(.has_stats) .directory_item.media[class*="_loaded"], .cue_sheet_track.selected
{ background-color:hsla(var(--media_background_h),var(--media_background_s),var(--media_background_l),1) !important; }
body:not(.has_stats) .directory_item.media.selected:not([class*="_loaded"]), .directory_item.media.hovered, .cue_sheet_track:not(.header):hover
{ background-color:hsla(var(--media_background_h),var(--media_background_s),var(--media_background_l),0.80) !important; }
body:not(.has_stats) .directory_item.media:not([class*="_loaded"]):hover
{ background-color:hsla(var(--media_background_h),var(--media_background_s),var(--media_background_l),0.70) !important; }
body.has_text_editor #show_text_editor, body.edited #show_text_editor
{ background-color:hsla(var(--text_editor_item_background_h),var(--text_editor_item_background_s),var(--text_editor_item_background_l),1.00); }
#preview_text_menu_item:hover { background-color:initial; }
/* TEXT COLORS */
a:hover, .text_color_default.selected, .selected .text_color_default { color:hsl(0,0%,var(--percent_05)); }
a, .text_color_default, #help_container, #help_container a { color:hsl(0,0%,var(--percent_10)); }
.text_color_333 { color:hsl(0,0%,var(--percent_25)); }
`;
const conditional_styles = `
/* PSEUDO-ELEMENTS */
#reload_btn.reset::before, #content_pane.has_zoom_image #reload_btn::before, #content_pane.has_scaled_image #reload_btn::before { content:"Reset"; }
.edited #show_text_editor a:after, .iframe_edited:not(.has_text_editor) #content_pane.has_iframe #content_title::after { content:" (edited)"; }
.theme_light #menu_theme span::before { content:"Light "; }
.theme_dark #menu_theme span::before { content:"Dark "; }
.show_details #show::before { content:"Hide "; }
#disable::after { content:"Enabled"; }
.disable_text_editing #disable::after { content:"Disabled"; }
#disable::after { content:"Enabled"; }
.is_error #directory_header_utilities { border-bottom:0; }
.is_error #menu_sort_by, .is_error #alternate_background, .is_error #show_numbers, .is_error #ignored_files, .is_error #autoload_files, .is_error #media_files, .is_error #make_playlist, .is_error #playlist_options, .is_error #open_font_file, .is_error #toggle_text_editing, .is_error #text_editing { display:none; }
#is_error { display:block !important; grid:none !important; grid-template-columns:none !important; }
#is_error_items { padding:6px 8px; }
.theme_dark #is_error_items { filter:invert(1); }
/* CONDITIONAL DISPLAY */
.is_error #directory_header_utilities > div:not(:first-of-type), .is_error #directory_footer, .is_non_local #show_invisibles_container, body:not(.has_media) #time, #close_playlist_container
{ display:none; }
.has_media #play_toggle, .theme_dark #theme_dark, .theme_light #theme_light, #content_pane[class^="has_"] #close_btn, #content_pane[data-content="has_text_editor"] #close_btn
{ display:unset; }
.has_playlist #stats_summary_playlist_files, .has_filelist #stats_summary_playlist_files
{ display:table-row; }
.has_menu #menu, .has_menu_parents #parents_links, #directory_menus .has_submenu:hover .submenu, #menu .has_submenu.hovered .submenu, #content_pane[data-content="has_font_file"] #font_file_viewer, #content_pane[data-content="has_glyph"] #font_file_viewer, #directory_footer li, .has_warning #overlay_container, .cue_sheet_track_list_container.has_cue_sheet, .has_playlist #close_playlist_container, .has_filelist #close_playlist_container { display:block; }
.has_stats .directory_item.invisible, .has_stats .directory_item.ignored, .has_stats .directory_item.ignored.hovered, .hide_ignored_items.has_stats .directory_item.ignored, body:not(.hide_ignored_items) .directory_item.ignored:not(.invisible) { display:grid; }
#content_pane[data-content="has_grid"] .split_btn, #content_pane[data-content="has_image"] .split_btn, #content_pane[data-content="has_font"] .split_btn, #content_pane[data-content="has_font_file"] #title_buttons_left .split_btn, #content_pane[data-content="has_glyph"] #title_buttons_left .split_btn, #content_pane[data-content="has_glyph"] #title_buttons_right .split_btn, body:not(.disable_text_editing) #content_pane[data-content="has_htm"] #open_in_text_editor,
#content_pane .content.has_content, #content_pane[data-content="has_text_editor"] #content_text, #content_pane[data-content="has_font"] #font_specimen, #content_pane[data-content="has_glyph"] #glyph_viewer, .has_playlist #close_playlist_btn_container, .has_filelist #close_playlist_btn_container
{ display:flex; }
#content_pane[data-content="has_text_editor"] .content.has_content, #content_pane[data-content="has_text_editor"] #content_grid, #content_pane[data-content="has_grid"] #content_text, #content_pane[data-content="has_grid"] .content.has_content { display:none !important; }
.theme_dark #menu li > span::before, .theme_dark #sorting span::before, .theme_dark #sorting span::after
{ filter:invert(1); }
#content_pane[data-content="has_ignored"]::before { opacity:0.3; }
.has_warning #directory_wrapper, .has_warning #content_pane, #close_audio:hover::after, .has_menu_parents #directory_list, .has_menu #directory_list, .faded:not(.has_stats) .directory_item:not(.hovered), .has_stats:not(.faded) .directory_item:not(.hovered), #parent_dir_menu:not(:hover), #menu_container:not(:hover) nav, #show_grid, #footer_utilities, .split_btn span, .disabled:not(.local), .focus_content .directory_item:not(.hovered) { opacity:0.6; }
.disabled:not(.local).selected { opacity:0.8; }
#show_grid:hover, #prev_next_btns span:hover, #show_grid:hover, #footer_utilities:hover, .split_btn span:hover
{ opacity:1.0; }
`;
const text_editor_styles = `
html, body, #iframe_body { margin:0; padding:0; height:100%; position:relative; font-family:${ $settings.UI_font }; font-size:${ $settings.UI_font_size }; }
button.focus, button:focus { outline:none; border-radius:3px !important; border-style:solid !important; border-width:1px !important; border-color:#222 !important; }
#iframe_body { position:relative; overflow:hidden; }
#iframe_body #content_text { display:flex; flex-direction:column; height:100%; position:absolute; z-index:1; width:100%; overflow:hidden;}
/* TOOLBAR */
#toolbar { width:100%; overflow-x:scroll; z-index:100; font-size:${ parseFloat($settings.UI_font_size) * 0.875 + $settings.UI_font_size.replace(/\d*/,'') }; -webkit-user-select:none; -moz-user-select:none; user-select:none; }
.toolbar_icon { margin:0 4px; padding:4px; min-width:16px; height:16px; cursor:pointer; display:flex; opacity:0.5; }
.text_editor_theme_dark .toolbar_icon:not(#save_btn):not(#clear_text), .text_editor_theme_dark #text_editor_preview_pane { filter:invert(1); }
#text_editor_sync_scroll { opacity:1; height:24px; padding:0 8px; flex-grow:unset; }
#text_editor_sync_scroll input { margin:0 4px 0 0; z-index:-1; }
#text_editor_sync_scroll label { white-space:pre; }
#save_btn ul { padding:0 32px 0 0; top:-3px; right:-4px; box-shadow:0px 4px 6px -3px #333; }
.edited #save_btn > div, .edited #save_btn ul div { color:red; }
#toolbar li:hover, .text_editor_raw #toggle_text_editor_raw, .text_editor_split_view #toggle_text_editor_raw, .text_editor_split_view:not(.text_editor_html) #toggle_text_editor_preview, .text_editor_preview #toggle_text_editor_preview, .text_editor_html #toggle_text_editor_html, .text_editor_split_view #toggle_text_editor_split_view { opacity:1; }
/* TEXT CONTENT CONTAINERS */
#text_container { overflow:hidden; }
#text_container .text_container_pane { padding:1em; width:100%; height:100%; overflow-y:scroll; box-sizing:border-box; border:0; z-index:1; background:transparent; line-height:1.2; font-size:${ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') }; }
#text_container .text_container_pane:focus { background-color:hsl(0,0%,var(--percent_95)); outline:none; box-shadow:inset 0px 0px 4px hsl(0,0%,var(--percent_60)); }
.text_editor_split_view #text_container .text_container_pane { width:50%; }
#text_container textarea { resize:none; font-family:monospace; }
/* SPLIT VIEWS */
.text_editor_raw #text_editor_raw_pane, .text_editor_preview:not(.text_editor_raw) #text_editor_preview_pane, .text_editor_html:not(.text_editor_raw) #text_editor_html_pane,
.text_editor_split_view #text_editor_raw_pane, .text_editor_preview.text_editor_split_view #text_editor_preview_pane, .text_editor_html.text_editor_split_view #text_editor_html_pane
{ display:block; }
/* THEMES & COLORS */
.text_editor_theme_default #content_text .background_grey_95, .background_grey_90:focus, #content_grid::after, #text_editor_preview_pane table th, #text_container
{ background-color:hsl(0,0,var(--percent_95)); }
.text_editor_theme_light #content_text .text_color_default, .theme_light.text_editor_theme_default #content_text .text_color_default
{ color:hsl(0,0%,var(--percent_05)); }
.text_editor_theme_dark #content_text .text_color_default { color:#EEE; }
/* custom previewed text styles */
#text_editor_preview_pane pre { border:solid 1px #CCC; border-radius:3px; white-space:pre-wrap; word-break:break-word; font-size:${ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') }; }
#text_editor_preview_pane th, #text_editor_preview_pane td { vertical-align:top; }
#text_editor_preview_pane blockquote { margin-top:1em; margin-bottom:1em; color:#555; }
#text_editor_preview_pane blockquote + blockquote { margin-top:0; }
.markdown_body input[type="checkbox"] { margin-top:0.375em; margin-right:6px; float:left; }
h1 .uplink,h2 .uplink,h3 .uplink,h4 .uplink,h5 .uplink,h6 .uplink { display:inline-block; font-size:0.875em; transition:opacity 0.25s; opacity:0; cursor:pointer; margin:0; padding:0; }
h1:hover .uplink,h2:hover .uplink,h3:hover .uplink,h4:hover .uplink,h5:hover .uplink,h6:hover .uplink
{ transition:opacity 0.25s; opacity:0.5; }
#text_editor_preview_pane table { font-size:inherit; }
.markdown_body table tr, .markdown_body .highlight pre, .markdown_body pre { background-color:transparent !important; }
.markdown_body::before, .markdown_body::after { display:none !important; background:transparent; }
/* disabled text editing */
#save_btn div, .comment, .disable_text_editing #toolbar,.disable_text_editing #text_editor_preview, .disable_text_editing #text_editing_handle
{ display:none; }
#iframe_body.has_warning::after { content:""; position:absolute; top:0; right:0; bottom:0; left:0; background:rgba(0,0,0,0.33); z-index:9998; }
/* TEXT EDITOR RESIZE HANDLE */
#text_editing_handle { display:none; width:8px; top:0; bottom:0; left:calc(50% - 4px); cursor:col-resize; z-index:3; }
#text_editing_handle::before { content:""; width:1px; background:#666; position:absolute; top:0; bottom:0; left:calc(50%); }
.text_editor_theme_dark #text_editing_handle::before { background:#111; }
.text_editor_split_view #text_editing_handle { display:block; }
`;
// Gecko (Firefos) Styles:
const gecko_style_rules = `
.dir::before { content:"" !important; display:none !important; }
.is_gecko button { padding:revert; }
.is_gecko #show_grid .menu { top:-7px; left:-120px; }
.is_gecko thead { font-size:100%; }
.is_gecko .directory_item.dir::before { position:absolute; }
.is_gecko .directory_item_name span { display:-webkit-box; width:auto; white-space:normal; }
.directory_item.dir td:not(:first-child), .directory_item.file td:not(:first-child) { width:unset !important; }
.is_gecko .directory_item td { min-width:calc(100% - 24px); }
.is_gecko .dir::before { content:"" !important; display:none !important; }
.is_gecko.use_default_icons:not(.is_converted_list) .directory_item.file .icon { padding-left:4px; background:none; }
.is_gecko.use_default_icons .directory_item.file .icon img { margin-right:6px; height:14px; }
.is_gecko #directory_list > tr > td:not(:first-of-type) { float:left }
.is_gecko #content_audio_title span { padding-top:6px;, padding-bottom:0; }
.is_gecko #audio,.is_gecko #audio_container { background-color:rgba(26,26,26,1); }
.is_gecko #prev_track, .is_gecko #next_track, .is_gecko #close_audio { filter:invert(1); border:none !important; }
.is_gecko #content_pane.has_zoom_image #content_image_container { display:block !important; }
`;
const safari_style_rules = `
.is_safari button { background-color:#FFF; }
.is_safari.theme_dark #prev_track, .is_safari.theme_dark #next_track, .is_safari.theme_dark #close_audio { filter:invert(1); }
`;
const chrome_style_rules = `
video::-webkit-media-controls-enclosure { border-radius:0 !important; }
`;
//==============================//
function addStyles(user_agent) { // ===> ADD STYLES
let styleEls = document.head.querySelectorAll('style, link[rel="stylesheet"], link[href$="css"]');
styleEls.forEach( el => el.remove() ); // remove any existing stylesheets
let default_styles = `<style id="main_styles">${ main_styles }</style> <style id="conditional_styles">${ conditional_styles }</style> <style id="color_and_background_styles">${ color_and_background_styles }</style> <style id="font_styles"></style> <style id="font_grid_styles"></style>`;
switch(user_agent) {
case user_agent === 'is_gecko': default_styles += `<style id="gecko_style_rules">${ gecko_style_rules }</style>`; break;
case user_agent === 'is_safari': default_styles += `<style id="safari_style_rules">${ safari_style_rules }</style>`; break;
case user_agent === 'is_chrome': default_styles += `<style id="chrome_style_rules">${ chrome_style_rules }</style>`; break;
}
document.querySelector('head').insertAdjacentHTML('beforeend', default_styles); // add assembled styles to head
}
// ***** END STYLES ***** //
//==============================//
// ***** INDEX PREP ***** //
// Try to determine index type from parent directory link container, with fallbacks for indexes that don't have parent directories, or for parent directory links that aren't siblings or ancestors of the index itself.
function getIndexType(agent) { // ===> GET INDEX TYPE
// let title = document.querySelector('title').innerText || '';
let index_el = document.querySelectorAll('body > ul, body > pre, body > table:last-of-type, body > div > table')[0];
let node_name = ( index_el !== undefined ? index_el.nodeName.toLowerCase() : 'body' ); // "body" is likely to be an error page
let types = {'gecko':'gecko','ul':'list','pre':'pre','table':'table','th':'table','td':'table','div':'default','error':'error','body':'error','permission_denied':'permission_denied'}; // object array of types
switch(true) {
// case ( /Error|404|Not Found/i.test(title) ): node_name = 'error'; break; // this method too brittle; breaks if a file or dir name contains "error"
case $protocol.startsWith('file'): if ( agent === 'is_gecko' ) { node_name = 'gecko'; } break;
}
return types[node_name]; // return index type
}
function getIndexItems(agent) { // ===> GET INDEX ITEMS
let type = getIndexType(agent), items; // get index type, define items
switch(type) {
// case 'error': items = document.getElementsByTagName('body')[0].innerHTML; break; // error type
case 'pre': items = document.querySelector('body > pre').innerHTML; break; // pre type
case 'list': items = document.querySelectorAll('body > ul li'); break; // list type
case 'gecko': items = document.querySelectorAll('body > table > tbody > tr'); break; // gecko type
case 'table': case 'td': // table types
switch(true) {
case document.querySelector('table > tbody') !== null: items = document.querySelectorAll('body table > tbody tr'); break; // ordinary tables
case document.querySelector('table > tbody') === null: items = document.querySelectorAll('body table tr'); break; // tables without tbody element
}
break;
case 'default': items = document.querySelectorAll('body > table > tbody tr'); break; // default: how is this different from table type?
}
return [items,type]; // return index items and index type
}
//==============================//
function convertPreType(items_str) { // ===> CONVERT PRE TYPE
let prepped_index = [];
const spaces = /\s{2,}/; // define regex for splitting rows; assumes pre-type only uses two or more spaces between "columns"
// remove header elements | link text nodes | links with empty text nodes (which are sometimes duplicated) | some parent & sorting links (href beginning with "?"; name, last modified, size, description)
items_str = items_str.replace(/[ ]*<(hr|img)[^>]*>[ ]*|\<dir\>/gm,' ') // remove various elements: img, hr, br
.replace(/<br>/gi,'\n')
.replace(/[ ]*<h\d>[^<]*<\/h\d>[ ]*|[ ]*(<a[^>]+?>)(Parent|Parent Directory|Up|Root)(<\/a>.+?$|[ ]*<a[^>]+?>\s*<\/a>[ ]*|[ ]*<a href="(\.*\/"|\?[^>]*?)>[^<]*<\/a>[ ]*)/gmi,'')
.replace(/[ ]*(<a[^>]+?>)[^<]*(<\/a>)/g,'$1$2 ') // remove link text nodes
.replace(/(\w)<a /g,'$1 <a ')
;
const items = items_str.split('\n'); // create array of item strings from items
for ( let i = items.length; i--; ) {
let prepped_item = [], link;
let cells = items[i].split(spaces);
for ( let j = cells.length; j--; ) {
let cell = cells[j];
if ( cell.trim().length > 0 ) {
if ( !cell.startsWith('<a ') ) { prepped_item.push(cell); } else { link = cell.split('"')[1]; } // extract link
}
}
if ( link === undefined || ( /^\.\.$|^\.\.\/$|^\/$|^\?|\?sort=|\?path=\&/mi.test(link) ) ) { prepped_item = []; } else { prepped_item.unshift(link); } // exclude some items (e.g., parent directory links)
if ( prepped_item.length > 0 ) { prepped_index.push(prepped_item); } // add prepped row to index
}
return prepped_index; // return prepped index
}
function convertListType(items) { // ===> CONVERT LIST TYPE
let prepped_index = [];
for ( let i = items.length; i--; ) {
let item = items[i];
if ( item.innerHTML.indexOf('Parent Directory') === -1 ) {
let prepped_item = [];
let link = item.querySelector('a').href;
item = item.innerHTML.replace(/<a .+?<\/a>\s*/,''); // remove <a> element up to first
let cells = item.split(' ');
for ( let cell of cells ) {
if ( cell.trim().length > 0 ) { prepped_item.push(cell); }
}
if ( link === undefined || ( /^\.\.$|^\.\.\/$|^\/$|^\?|\?sort=|\?path=\&/mi.test(link) ) ) { prepped_item = []; } else { prepped_item.unshift(link); } // exclude some items (e.g., parent directory links)
if ( prepped_item.length > 0 ) { prepped_index.push(prepped_item); }
}
}
return prepped_index; // return prepped index
}
function convertGeckoType(items) { // ===> CONVERT GECKO TYPE
let prepped_index = [];
for ( let item of items ) {
let prepped_item = [], cellContents = '', cells = item.cells, link = item.innerHTML.split('href=\"')[1].split('\">')[0];
for ( let cell of cells ) {
cellContents = cell.innerText;
cellContents = ( cellContents !== undefined ? cellContents.trim() : '');
prepped_item.push(cellContents);
}
prepped_item[1] = prepped_item[1].replace(/\s*KB/,'000'); // convert reported size in KB to total bytes
prepped_item[2] = prepped_item[2] + ' '+ prepped_item[3];
prepped_item = prepped_item.slice(1,-1);
if ( link === undefined || ( /^\.\.$|^\.\.\/$|^\/$|^\?|\?sort=|\?path=\&/mi.test(link) ) ) { prepped_item = []; } else { prepped_item.unshift(link); } // exclude some items (e.g., parent directory links)
if ( prepped_item.length > 0 ) { prepped_index.push(prepped_item); }
}
return prepped_index; // return prepped index
}
function convertTableType(type,items) { //*** for local chrome indexes and server-generated table-type indexes // ===> CONVERT TABLE TYPE
const testString = new RegExp(/<hr>|<th|href=\"\?|href=\"\/\"|href="\.\.\/"|alt=\"\[PARENTDIR\]|>\s*Parent Directory\s*<|>\s*\ \s*<|^\s*-\s*$|\?sort=|\?path=\&|^<img|<dir>/,'mi');
let prepped_index = [], items_length = items.length, prepped_item, cells, cells_length, i, j;
for ( i = 0; i < items_length; i++ ) {
prepped_item = []; cells = items[i].cells, cells_length = cells.length;
if ( items[i].querySelector('a') !== null && ( !/^\?|^\.\.\/$|^\/$/m.test(items[i].querySelector('a').href ))) { prepped_item.push(items[i].querySelector('a').href); } // get link and add to prepped_item
for ( j = 0; j < cells_length; j++ ) {
if ( cells[j].childElementCount === 0 && !/^\s*\ \s*$/m.test(cells[j].innerHTML) && !testString.test(cells[j].innerHTML) ) { // exclude various cells
prepped_item.push( cells[j].innerText.trim().replace(/(^[ ]*-[ ]*$|[ ]*-[ ]*\ [ ]*$)/m,'—') ); // get content from other cells // replace empty with dash
}
}
if ( prepped_item.length > 1 ) { prepped_index.push(prepped_item); } // prepped_item.length > 2 in order to omit parent directory row
}
return prepped_index; // return prepped index
}
function convertErrorType(items) { return items; } // ===> CONVERT ERROR TYPE; receives and returns html string
function convertPlaylist(items) { // ===> CONVERT PLAYLIST items
let prepped_index = []; let prepped_item; let items_arr, type;
items = items.replace(/\s*#EXTM3U.*\s*/g,'').replace(/^\*\n{2,}/gm,'\n').replace(/\.pdf\?.+?\n/g,'.pdf\n').replace(/\?/g,'%3F'); // remove header comment and multiple returns
switch(true) { // determine playlist type;
case ( /#EXTINF:/i.test(items) ): type = 'extm3u'; items_arr = items.split('#EXTINF:'); break; // rows made by splitting at "#EXTIMG:" prefix
default: type = 'm3u'; items_arr = items.split('\n'); break; // rows are just naked links
}
items_arr.forEach(function(item) {
switch(true) { // get entry information: title, link, etc.
case type === 'extm3u': item = item.trim().split('\n'); if ( item[1] !== undefined ) { prepped_item = [item[1],'','']; } break; // split into info (row[0]) & link, but we only use the link anyway
case type === 'm3u': prepped_item = [item,'','']; break; // m3u with urls only
}
if ( prepped_item !== undefined ) { prepped_index.push(prepped_item); }
});
return prepped_index; // return prepped index
}
function convertIndexItems(type,items) { // ===> CONVERT INDEX ITEMS by type; returns [prepped_index]
let converted = [];
switch(type) {
case 'gecko': converted = convertGeckoType(items); break;
case 'list': converted = convertListType(items); break;
case 'pre': converted = convertPreType(items); break;
case 'table':
case 'default': converted = convertTableType(type,items); break;
case 'error': converted = convertErrorType(items); break;
}
return converted;
}
//==============================//
function buildNewIndex(id,prepped_index,sort,type,body_id) { // ===> BUILD NEW INDEX from prepped rows
let new_index_items = [], body_classes = new Set();
let index_html = '';
let new_item, item, item_info = [], item_link, item_name, item_sort_name, item_size_and_date, item_size, item_sort_size, item_date, item_sort_date, item_ext, item_sort_kind, item_kind, item_classes;
let name_span, cell_link, cell_name, cell_size, cell_date, cell_kind, cell_ext, cell_time, prepped_index_length = prepped_index.length, item_disabled, item_input;
let stats, stats_classes = [], stats_kinds = [];
let dir_list_parent_class = ( ( body_id === (null || 'top') || type === 'playlist' ) ? 'top_item' : 'iframe_item' ); // body_id used to set dir list details style
let parent_id = ( getSearchParam('parent_id') || '' ), connector = ( getSearchParam('parent_id') ? '_' : '' ), level = ( Number(getSearchParam('level')) || 0 ), level_style = ( level === 0 ? '' : `style="padding-left:${ Number(level) * 22 }px;"` ); // ensure unique ids and set indents for subdirectory items
switch(type) { // add body classes according to index type
case 'error': body_classes.add('is_error'); break;
case 'pre': body_classes.add('is_converted_pre'); break;
case 'list': body_classes.add('is_converted_list'); break;
case 'gecko': body_classes.add('is_converted_gecko'); break;
case 'table': case 'td': body_classes.add('is_converted_table'); break;
case 'default': body_classes.add('is_default'); break;
}
// create and format directory row
for ( let i = prepped_index_length; i--; ) {
item = prepped_index[i];
item_info = getLinkInfo(item[0]); // = [link,name,ext,kind,item_classes,body_classes];
item_link = item_info[0];
item_name = item_info[1]; // prep display name, with word breaks added after unbreakable chars
item_sort_name = escapeStr( item_info[1].toLocaleLowerCase() );
item_size_and_date = getItemSizeAndDate(item);
item_size = item_size_and_date[0];
item_sort_size = item_size_and_date[1];
item_date = item_size_and_date[2];
item_sort_date = item_size_and_date[3];
item_ext = item_info[2];
item_kind = item_info[3];
item_sort_kind = item_info[3];
item_classes = item_info[4] +" "+ dir_list_parent_class;
item_disabled = ( item_classes.indexOf('local') > -1 || type === 'gecko' ? ' disabled="disabled"' : '' );
item_input = ( /audio|video/.test(item_kind) ? '<input class="directory_item_input" type="checkbox" tabindex="-1" checked="true" ${ item_disabled } autocomplete="off" />' : '' );
// Assemble row elements
name_span = `<span class="has_icon_before_before"></span><span class="name_span display_flex">${ item_input }${ item_name }</span>`;
cell_link = `<a href="${ item_link }" class="icon directory_item_name name directory_item_name_a text_color_default position_relative">${ name_span }</a>`;
cell_name = `${ cell_link }`;
cell_time = `<span class="directory_item_media_duration align_right" data-duration=""></span>`;
cell_size = `<span class="directory_item_details size details" data-size="${ item_sort_size }">${ item_size }</span>`;
cell_date = `<span class="directory_item_details date details" data-date="${ item_sort_date }">${ item_date }</span>`;
cell_kind = `<span class="directory_item_details kind details" data-kind="${ item_sort_kind }">${ item_kind }</span>`;
cell_ext = `<span class="ext details" data-ext="${ item_ext }"></span>`;
// Assemble rows
new_item = `<li id="${ parent_id }${ connector }rowid-${ (prepped_index.length - i) }" class="directory_item ${ item_classes }" data-name="${ item_sort_name.split("/")[0] }" data-kind="${ item_sort_kind }" data-ext="${ item_ext }" data-level="${ level }" ${ level_style }>${ cell_name } ${ cell_time } ${ cell_size } ${ cell_date } ${ cell_kind } ${ cell_ext }</li>\n`;
new_index_items.push(new_item); // add item to index items
body_classes.add(item_info[5].join(' ')); // add item classes to body_classes
if ( /audio|video/.test(item_sort_kind) ) { // if media item...
getMediaDuration( item_link, item_sort_kind, function( duration ) { // get media duration
let item_id = parent_id + connector +'rowid-'+ ( prepped_index.length - i);
if ( window.location.search.indexOf('subdirectory') > -1 ) {
sendMessage('top','set_media_duration','', [ item_id, item_sort_kind, duration ] ); // if subdirectory, send message from utility iframe to top
} else {
setMediaDuration( item_id, item_sort_kind, duration ); // else set media duration
}
if ( duration === 0 ) { addClass('#'+ item_id,'disabled'); } // if duration === 0, disable media item
});
}
stats_kinds.push(item_kind); // add item kind to stats_kinds
stats_classes.push(item_info[6]); // add item classes for stats
}
stats = buildStats(stats_classes,stats_kinds); // build stats
// Sort items
if ( sort === '' || sort === undefined ) { sort = getSearchParam('sort_by'); } // get sort_by pref
let sort_direction = getSearchParam('sort_direction'); // get sort_direction pref
let sorted_index_items = sortDirList(new_index_items, 'sort_by_'+ sort, sort_direction); // make initial sort
return [sorted_index_items, Array.from(body_classes).join(' '),stats,index_html]; // return [sorted_index_items, body_classes, stats, index_html]
}
//==============================//
function getLinkInfo(link) { // ===> GET LINK INFO; returns [link,name,ext,kind,item_classes,body_classes]
switch(true) {
case link === undefined: return; // return if link undefined
case link === null: link = document.getElementById('content_iframe').src; break; // link from opening local link files links in iframe
case link.startsWith('file://') && window.location.protocol === 'file:':link = link.split('file://')[1]; break; // local links
case link.startsWith('/') && window.location.protocol === 'file:': link = 'file://'+ link; break; // local links
case !link.startsWith('/') && !link.endsWith('/') && !/\./.test(link): link = '/'+ link +''; break;
}
if ( /\.php\?(path)=/.test(link) ) { link = link.split('=')[1]; } // attempt to deal with some php links
if ( /%3C/.test(link) ) { link = link.replace(/%3C/g,'\<'); } // replace "<" with html entity
let URL = newURL(decodeURIComponentSafe(encodeURIComponent(link)));
let prepped_link, display_name, kind, ext, item_classes = [], body_classes = [], stats_classes = [], aliases = new RegExp(/(symlink|alias|symbolic link)$/,'m');
switch(true) { // prep link
case $protocol !== 'file:': // for non-local pages
switch(true) {
case URL.protocol === 'file:': case URL.protocol === undefined: prepped_link = link; item_classes.push('local','ignored'); break; // local links from non-local pages
default: prepped_link = URL.href; // non-local pages
} break;
default: // for local pages;
switch(true) {
case URL.protocol !== 'file:': prepped_link = URL.href; break;
default: prepped_link = URL.pathname;
}
}
switch(true) { // prepare display name, body_classes, and item_classes
case URL.pathname.endsWith('/'): // dirs and apps
display_name = URL.pathname.split('/').reverse()[1] + '/';
switch(true) {
case /\.app$|\.app\/$|\.exe$/m.test(display_name): // apps
ext = 'app'; kind = ext;
if ( $settings.apps_as_dirs === false ) { item_classes.sort().unshift('file','app'); } else { item_classes.sort().unshift('dir','app'); }
break;
default: kind = 'dir'; ext = 'dir'; // dirs
item_classes.unshift(kind); // remove kind from item_classes
}
item_classes.push('not_media'); // add "not_media" to item_classes
if ( display_name.startsWith('.') ) { item_classes.push('invisible'); stats_classes.push('invisible'); } break;
default: // files
display_name = prepped_link.trim().split('/?')[0].split('/').reverse()[0];
switch(true) {
case display_name.toLowerCase().endsWith('symlink'): ext = 'symlink'; break;
case !/\./.test(display_name): ext = display_name.toLowerCase(); break; // if no '.' in link (typical for bin files), ...
default: // find the last . and get the remaining characters
ext = display_name.toLowerCase().slice(display_name.toLowerCase().lastIndexOf('.') + 1);
for ( let item_kind in $item_kind ) { if ( $item_kind[item_kind].includes( ext ) ) { kind = item_kind; } } // kind = types
if ( /url|url\/|webloc|webloc\//.test(ext) ) { kind = 'link'; } // links
switch(true) {
case kind === 'audio': item_classes.push('media'); body_classes.push('has_media','has_audio'); break;
case kind === 'video': item_classes.push('media'); body_classes.push('has_media','has_video'); break;
case kind === 'font' : body_classes.push('has_fonts'); break;
case kind === 'image': body_classes.push('has_images'); break;
}
if ( $row_settings.ignored.includes( ext ) ) { item_classes.push('ignored'); stats_classes.push('ignored'); }
if ( display_name.startsWith('.') ) { item_classes.push('invisible'); stats_classes.push('invisible'); }
}
if ( kind === undefined ) { kind = 'other'; }
if ( !/audio|video/.test(kind) ) { item_classes.push('not_media'); }
item_classes.unshift(kind); item_classes.unshift('file');
}
stats_classes.push(kind);
if ( ext === undefined ) { ext = ''; }
if ( aliases.test(display_name) ) { item_classes.push('alias'); }
for ( let item_kind_system of $item_kind.system ) { if ( display_name.endsWith(item_kind_system) ) { item_classes.push('ignored'); } } // ignore various system items
item_classes = item_classes.sort();
item_classes = Array.from(new Set(item_classes)).join(' '); // remove dupe classes
return [decodeURIComponentSafe(encodeURIComponent(prepped_link)).trim(),decodeURIComponentSafe(display_name).trim(),ext,kind,item_classes,body_classes,stats_classes.join(' ')];
}
//==============================//
function getItemSizeAndDate(cells) { // ===> GET ITEM SIZE AND DATE
let item_size_and_date = [], row_display_size, item_sort_size, item_display_date, item_sort_date, size_units = /[BYTES|B|K|KB|MB|GB|TB|PB|EB|ZB|YB]/;
if ( cells.length > 1 ) { // test for typical date/time separators.
if ( /[-:\/]/.test(cells[1]) ) { item_display_date = cells[1]; row_display_size = cells[2]; } else { item_display_date = cells[2]; row_display_size = cells[1]; }
}
switch(true) { // get size
case row_display_size !== undefined && row_display_size.toLowerCase() === 'dir':
case ( /undefined|—|-|,|\*/.test(row_display_size) ):
case row_display_size === '': // if size is undefined, empty, or punctuation
row_display_size = '—'; item_sort_size = '0'; // if no size supplied, use these defaults
break;
default:
item_sort_size = getItemSortSize(row_display_size);
switch(true) {
case !row_display_size.toUpperCase().match(size_units) : // if provided size is only numeric
row_display_size = formatBytes(row_display_size,1); // format byte size
break;
default:
row_display_size = row_display_size.replace('K','k').replace(/(\d+)\s*([A-z])/,'$1 $2'); // ensure display size has space between number and units
}
break;
}
if ( row_display_size === 'NaN undefined' ) { row_display_size = '0 B'; }
// get date
if ( [undefined,'','-'].includes(item_display_date) ) { item_display_date = '—'; item_sort_date = '0'; } else { item_sort_date = getItemDate(item_display_date); }
item_display_date = item_display_date.replace(/, (.+)/,'<wbr>, $1').replace(/ (AM|PM)$/im,'<wbr> $1').replace(/\s/g,' '); // ensure that time acts as a block for wrapping in narrow sidebar
item_size_and_date.push( row_display_size, item_sort_size, item_display_date, item_sort_date );
return item_size_and_date;
}
function getItemSortSize(val) { // GET ITEM SORT SIZE
let sort_size, values = val.replace(/(\d+)\s*([A-z]+)/,'$1 $2').split(' '), size = values[0], unit = values[1];
const factor = { undefined:1, '':1, B:1, K:1000, KB:1000, M:1000000, MB:1000000, G:1000000000, GB:1000000000, T:1000000000000, TB:1000000000000, P:1000000000000000, PB:1000000000000000, E:1000000000000000000, EB:1000000000000000000, Z:1000000000000000000000, ZB:1000000000000000000000 }; // unit to file size
if ( unit !== undefined ) { unit = unit.toUpperCase(); }
sort_size = size * factor[unit]; // convert byte size to multiplication factor
return sort_size;
}
function formatBytes(val, decimals) { // ===> FORMAT BYTES: format numeric sizes for display
const k = 1024, dm = (decimals < 0 ? 0 : decimals), sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], i = Math.floor(Math.log(val) / Math.log(k));
if (val === 0) { return '0 Bytes'; } else { return parseFloat((val / Math.pow(k, i)).toFixed(dm)) +' '+ sizes[i]; }
}
function processDate(match,p1,p2,p3) { //***date formats: 2017-10-09 13:12 || 2015-07-25T02:02:57.000Z || 12-Mon-2017 21:11 ***// // ===> PROCESS DATE
const mo = 'JanFebMarAprMayJunJulAugSepOctNovDec'.indexOf(p2)/3 + 1; // e.g., convert month into number, or use number
return p3 +'-'+ mo +'-'+ p1; // return assembled date: YYYY-MM-DD
}
function getItemDate(val) { // ===> GET ITEM DATE: for sorting (YYYY-MM-DD)
let sort_date = val.replace(/^(\d{2})-(\w{3})-(\d{4})/m, processDate) // convert Month to number
.replace(/\b(\d{1})[-:/]/g,'0$1/') // add leading 0 for single digit numbers
.replace(/(\d{2})\/(\d{2})\/(\d{2}),/,'$3$1$2') // reorder MM/DD/YY dates to YY/MM/DD
.replace(/-|:|\s+|\//g,''); // remove spacing characters
return sort_date;
}
//==============================//
var getFormattedDuration = (secs) => { // ===> GET FORMATTED TIME
let sec_num = parseInt(secs, 10), hours = Math.floor(sec_num / 3600), minutes = Math.floor(sec_num / 60) % 60, seconds = sec_num % 60;
let formattedTime = [hours,minutes,seconds].map( v => v < 10 ? "0" + v : v ).filter( (v,i) => v !== "00" || i > 0 ).join(":");
formattedTime = formattedTime.replace(/^0/m,''); // remove initial 0
return formattedTime;
};
function getMediaDuration(link, kind, mediaInfoCallback) { // ===> GET MEDIA DURATION
if ( !/audio|video/.test(kind) || link === undefined ) { return; } // abort if not a media file
let media = document.createElement(kind); // create audio or video element
media.src = link; // set the media element source
media.onloadedmetadata = function() { mediaInfoCallback( media.duration ); media = null; } // return media_info = [duration, kind]
media.onerror = function() { mediaInfoCallback( 0 ); }; // media.onerror = function() { callback( new Error("File not found")); };
}
function setMediaDuration(id,kind,duration) { // ===> SET MEDIA DURATION
setAttribute('#'+ id +' .directory_item_media_duration','data-duration',duration);
if ( document.getElementById(id) !== null ) { document.querySelector('#'+ id +' .directory_item_media_duration').innerText = getFormattedDuration(duration); } // add time to dir_list row details
let totalDurationEl = document.getElementById('total_duration'), total_duration;
if ( totalDurationEl !== null ) {
total_duration = Number( totalDurationEl.getAttribute('data-total_duration') );
total_duration += Number(duration);
setAttribute('#total_duration','data-total_duration',total_duration);
document.getElementById('total_duration').innerText = getFormattedDuration(total_duration);
}
addClass('body','has_media'); // why is this needed here? subdirs?
}
function updateDurations(bool) { // bool === true: don't bother updating stats details, since they aren't visible // ===> UPDATE DURATIONS
let media_items = document.querySelectorAll('.directory_item.media');
if (media_items.length === 0 ) { removeClass('body','has_media has_audio has_video'); return; }
let kind, total_duration = 0, duration = 0, audio_duration = 0, video_duration = 0;
for ( let i = 0; i < media_items.length; i++ ) { // get classes and kind for each item
kind = media_items[i].getAttribute('data-kind'); // = [link,name,ext,kind,item_classes,body_classes];
duration = Number(media_items[i].getElementsByClassName('directory_item_media_duration')[0].getAttribute('data-duration'));
setMediaDuration(media_items[i].getAttribute('id'),kind,duration );
total_duration += duration;
setAttribute('#total_duration','data-total_duration',total_duration);
document.getElementById('total_duration').innerText = getFormattedDuration(total_duration);
addClass('body','has_media');
switch(true) { // update stats details
case bool === true: break;
case kind === 'audio': addClass('body','has_audio');
audio_duration += Number(duration);
document.getElementById('stats_details').querySelectorAll('span.audio')[0].setAttribute('data-audio_duration',' (Total Time: '+ getFormattedDuration(audio_duration) +')');
break;
case kind === 'video': addClass('body','has_video');
video_duration += Number(duration);
document.getElementById('stats_details').querySelectorAll('span.video')[0].setAttribute('data-video_duration',' (Total Time: '+ getFormattedDuration(video_duration) +')');
break;
}
}
}
//==============================//
function buildStats(stats_classes,stats_kinds) { //*** BUILD STATS
stats_classes.sort();
let total_items = stats_classes.length, counts = {}, kinds = [], stats_rows = [], total_dirs = 0, total_files = 0, total_dirs_invisible = 0, total_files_invisible = 0, total_invisibles = '';
for ( let i = 0; i < total_items; i++ ) {
stats_classes[i] = stats_classes[i].split(' ').reverse().join(' ') // reorder classes to make invisible/ignored last
counts[stats_classes[i]] = 1 + ( counts[stats_classes[i]] || 0 ); // get key/value pairs for item_classes/total counts
switch(true) {
case ( !/invisible|ignored/.test(stats_classes[i]) ): break; // don't count :not(.invisible) and :not(.ignored)
case (getSearchParam('show_invisibles') === 'true' ) && ( getSearchParam('hide_ignored_items') === 'true' ): // show_invisibles && hide_ignored
if ( /invisible/.test(stats_classes[i]) && /ignored/.test(stats_classes[i]) ) { break; } // don't count .invisible.ignored
if ( /dir/.test(stats_classes[i]) ) { total_dirs_invisible++; } else { total_files_invisible++; } break; // else count .ignored
case (getSearchParam('show_invisibles') === 'false' ) && ( getSearchParam('hide_ignored_items') === 'true' ): // hide_invisibles && hide_ignored (hide all)
if ( /dir/.test(stats_classes[i]) ) { total_dirs_invisible++; } else { total_files_invisible++; } break; // count .invisible and .ignored (count all)
case (getSearchParam('show_invisibles') === 'true' ) && ( getSearchParam('hide_ignored_items') === 'false' ): // show_invisibles && show_ignored (show all)
if ( /invisible/.test(stats_classes[i]) || /ignored/.test(stats_classes[i]) ) { break; } break; // don't count .invisible or .ignored (count none)
case (getSearchParam('show_invisibles') === 'false' ) && ( getSearchParam('hide_ignored_items') === 'false' ): // hide_invisibles && show_ignored
if ( !/invisible/.test(stats_classes[i]) && /ignored/.test(stats_classes[i]) ) { break; } // don't count .ignored:not(.invisible)
if ( /dir/.test(stats_classes[i]) ) { total_dirs_invisible++; } else { total_files_invisible++; } break; // else count .invisible and .invisible.ignored
}
}
for ( let i = 0; i < stats_kinds.length; i++ ) { kinds[stats_kinds[i]] = 1 + ( kinds[stats_kinds[i]] || 0 ); } // get key/value pairs for item kinds/counts
total_dirs = ( kinds.dir || 0 ); total_files = ( total_items - total_dirs ); // total dirs && files count
if ( getSearchParam('show_invisibles') === 'false' || getSearchParam('hide_ignored_items') === 'true' ) {
total_invisibles = ' (+'+ (total_dirs_invisible + total_files_invisible) +')';
total_items = total_items - (total_dirs_invisible + total_files_invisible);
total_dirs = total_dirs - total_dirs_invisible;
total_files = total_files - total_files_invisible;
}
for ( let count in counts ) { // make detail row for each kind of item --> doesn't preserve order
let kinds_items = count.split(' '), stats_row_kinds = '';
kinds_items.forEach( item => ( stats_row_kinds += (`<span class="${ item }" >${ item }</span>`)) ); // create kind span
let stats_row = `<li class="stats_list_item ${ count } hover_bold" data-kind="${ kinds_items }"><span class="stats_list_item_name name stats_count">
<a class="icon stats_list_item_name_a display_flex text_color_default" data-count="${ counts[count] }"><span class="stats_list_item_name_a_span has_icon_before stats_kind">${ stats_row_kinds }</span></a>
</span></li>`;
stats_rows.push(stats_row);
}
stats_rows.sort();
let stats = `<nav id="stats_container" class="display_flex"><div id="stats" class="normal pointer">
<ol id="stats_summary" class="background_grey_80 text_color_default margin_0 padding_0"> <li class="stats_list_item padding_0"> <span id="stats_summary_totals">${ total_items } Items${ total_invisibles }: ${ total_dirs } Directories, ${ total_files } Files</span> <span id="total_duration" class="display_none" data-total_duration=""></span> </li> </ol>
<ol id="stats_summary_detailed" class="border_bottom position_relative background_grey_80 text_color_default margin_0 padding_0 display_none"> <li id="stats_summary_detailed_total" class="summary_detailed border_bottom hover_bold"> <span>${ total_items } Items (${ total_dirs_invisible + total_files_invisible } invisible or ignored)</span> </li> <li id="stats_summary_detailed_dirs" class="stats_list_item dir summary_detailed background_grey_85 hover_bold padding_0"> <span class="stats_list_item_name name stats_count"> <a class="icon stats_list_item_name_a display_flex text_color_default" data-count="${ total_dirs }"> <span class="stats_list_item_name_a_span has_icon_before stats_kind">Dirs (${ total_dirs_invisible } invisible or ignored)</span> </a> </span> </li> <li id="stats_summary_detailed_files" class="stats_list_item file summary_detailed background_grey_85 hover_bold padding_0"> <span class="stats_list_item_name name stats_count"> <a class="icon stats_list_item_name_a display_flex text_color_default" data-count="${ total_files }"> <span class="stats_list_item_name_a_span has_icon_before stats_kind">Files (${ total_files_invisible } invisible or ignored)</span> </a> </span> </li> </ol>
<div id="stats_details_container"> <ol id="stats_details" class="margin_0 padding_0 position_relative display_none"> ${ stats_rows.join('\n') } </ol> </div>
</div></nav>`;
return stats;
}
function updateStats() { // ===> UPDATE STATS
let items = document.querySelectorAll('.directory_item'); // get all dir_list items
let stats_classes = [], stats_kinds = [], item_classlist = []; // define various arrays
let item_info;
for ( let i = 0; i < items.length; i++ ) { // get classes and kind for each item
item_info = getLinkInfo( items[i].getElementsByClassName('directory_item_name_a')[0].href ); // get item info = [link,name,ext,kind,item_classes,body_classes];
item_classlist = item_info[4]; // get item_classlist
item_classlist = item_classlist.replace(/file|media|audio_loaded|content_loaded|has_subdirectory|selected|not_/g,'').trim(); // remove unwanted classes --> why file and media?
stats_classes.push(item_classlist); // add item_classlist to stats_classes
stats_kinds.push( item_info[3] ); // add item_kind to stats_kinds
}
document.getElementById('stats_container').remove(); // remove old stats
document.getElementById('directory_footer').insertAdjacentHTML( 'afterbegin',buildStats(stats_classes,stats_kinds) ); // build new stats and add to directory_footer
updateDurations(true); // update after building stats
initStatsEventListeners(); // initial event listeners for new stats items
}
// ***** END DIR_LIST SETUP ***** //
//============================//
// ***** UI SETUP ***** //
function prepDocHead(agent) { // ===> PREP DOC HEAD
document.title = 'Index of: '+ current_location; // change the doc title to current location
document.querySelectorAll('head script, head style, head link').forEach( headEl => headEl.remove() ); // remove any existing scripts
document.querySelector('head').insertAdjacentHTML('afterbegin','<meta charset="utf-8"><base href="'+ window.location.origin +'">');
document.querySelector('head title').removeAttribute('id');
if ( window.location.protocol.startsWith('file') ) { // add custom favicon for local directories
document.querySelector('head').insertAdjacentHTML('afterbegin','<link href="data:image/png;base64,' + get_SVG_UI_File_Icon('favicon') +'" rel="icon" sizes="16x16" />');
}
addStyles(agent); // add styles
}
function getUIPrefBodyClasses(agent) { // ===> GET UI PREF BODY CLASSES and other initial settings
let queries = new URLSearchParams(window.location.search).entries(); // make new search params from window.location.search
queries = Object.fromEntries(queries);
let body_classes = [], settings = Object.assign({},queries,$settings); // merge $settings and query settings
for ( let key in settings ) {
switch(true) {
case ['bookmarks','grid_font_size','grid_image_size','UI_font','UI_font_size'].includes(key): break; // ignore these keys (values set in css or by setUpTextEditorUI)
case key === 'enable_text_editing': if ( getSearchParam(key) === 'true') { body_classes.push(key); } else { body_classes.push('disable_text_editing'); } break;
case key === 'text_editor_theme':
switch(true) {
case getSearchParam('text_editor_theme') === 'default': body_classes.push( 'text_editor_theme_default' ); break;
// case getSearchParam('text_editor_theme') === 'default': body_classes.push( 'text_editor_theme_'+ getSearchParam('theme');
case getSearchParam('text_editor_theme') === 'light': body_classes.push( 'text_editor_theme_light' ); break;
case getSearchParam('text_editor_theme') === 'dark': body_classes.push( 'text_editor_theme_dark' ); break;
}
break;
case ['text_editor_default_view'].includes(key): body_classes.push('has_'+ getSearchParam(key) ); break; // some non-booleans: class = search_param value
case ['sort_direction'].includes(key): body_classes.push( getSearchParam(key) ); break; // some non-booleans: class = search_param value
case ['theme','sort_by'].includes(key): body_classes.push( key +'_'+ getSearchParam(key) ); break; // other non-booleans: class = key + value
case getSearchParam(key) === 'true': body_classes.push(key); break; // booleans: only add the key to body classes
}
}
body_classes.push(agent); body_classes.push('is_'+getOS()); // add browser and os classes
return body_classes.join(' ');
}
function makeNewIndex(el,sort,agent,body_id) { // ===> MAKE NEW INDEX
const index_items = getIndexItems(agent), items = index_items[0], type = index_items[1];
const converted_index = convertIndexItems( type, items ); // = array of rows: ["link","date","size"]
switch(type) {
case 'error': return [[['<tr id="is_error"><td id="is_error_items">'+ items +'</td></tr>'],'is_error'],''];
default: { let new_index = buildNewIndex( el.id, converted_index, sort, type, body_id ); return [new_index]; }
}
}
function buildUI() { // ===> BUILD UI: Append all assembled elements to $body
const agent = getBrowser();
switch(true) {
case isTopWindow(): { // if it's not an iframe...
const make_new_index = makeNewIndex('body','',agent,'top'); // make index
let body_classes = make_new_index[0][1] +' '+ getUIPrefBodyClasses(agent); // delete extra spaces, create array of body class names
let main_content = `${ Directory_Elements('top') } ${ Content_Pane_Elements } ${ Utilities_Elements('top') }`;
main_content = main_content.replace(/insert_prepped_index/,make_new_index[0][0]).replace(/insert_stats/,make_new_index[0][2]); // add dir_index and stats to MainContent
document.body.innerHTML = ''; // remove body contents
prepDocHead(agent); // add title, favicon, meta tags, styles to head
setAttribute('body','id','top'); // add body id
setAttribute('body','lang','en'); // add body lang attr
if ( document.body.getAttribute('class') !== null ) { document.body.removeAttribute('class'); } // remove body classes, if any
addClass('body',body_classes); // add body classes
document.body.innerHTML = main_content; // add main content to body
if ( make_new_index[0][1] !== 'is_error' ) { initEventListeners(); autoLoadFile(); } else { initBaseEventListeners(); } // initialize event listeners & autoloadfiles if not error page
break;
}
case !isTopWindow() && !window.location.pathname.endsWith('.pdf'): setUpIframeUI(agent); break; // if iframe and not pdf, setup iframe UI
}
if ( getSearchParam('show_image_thumbnails') === 'true' ) { showImageThumbnails(); } // load image thumbnails after building ui
}
buildUI(); // CALL BUILDUI
//============================//
function defaultSettings(e) { // ===> DEFAULT SETTINGS: remove queries;
if (window.confirm( 'Are you sure you want to remove all your temporary UI settings from the URL query string?' ) ) {
e.preventDefault(); e.stopPropagation();
removeClass('body','has_menu');
let query_str = '';
if ( getSearchParam('selected') !== undefined ) { query_str += 'selected='+ getSearchParam('selected'); }
if ( getSearchParam('history') !== undefined ) { query_str += 'history='+ getSearchParam('history'); }
if ( query_str.length > 0 ) { query_str = '?' + query_str.replace(/\s/g,'+'); }
window.location.assign(current_location + query_str);
}
}
function exportSettings() { // ===> EXPORT SETTINGS
removeClass('body','has_menu');
const settings_string = ( JSON.stringify($settings,null,'\t'));
saveSettings('settings.json',settings_string);
}
function saveSettings(file_name, data) { saveFile(data,'application/json',file_name); } // ===> SAVE SETTINGS
//============================//
// INITIALIZE EVENT LISTENERS
function initEventListeners() { // ===> INIT EVENT LISTENERS
initBaseEventListeners(); // init base event listeners
initStatsEventListeners(); // init stats event listeners
document.getElementById('default_settings').onclick = function(e) { defaultSettings(e); }; // settings to default
document.getElementById('export_settings').onclick = function(e) { e.preventDefault(); e.stopPropagation(); exportSettings(); }; // export settings
document.getElementById('open_font_label').onclick = function(e) { e.stopPropagation(); }; // open font file (req: opentype.js font parsing)
document.getElementById('open_font').onchange = function(e) { e.stopPropagation(); openFile(e,'font'); }; // open font
document.getElementById('save_svg').onclick = function(e) { e.stopPropagation(); saveGlyph(); }; // save glyph as svg
document.getElementById('view_directory_source').onclick = function(e) { e.stopPropagation(); showWarning( 'showDirectorySource' ); }; // toggle show directory source
document.getElementById('open_in_content_pane').onclick = function() { showWarning( 'openSidebarInContentPane' ); }; // open sidebar in content pane
document.getElementById('open_in_text_editor').onclick = function(e) { e.preventDefault(); openInTextEditor(); this.blur() }; // sendMessage, the receiveMessage from iframe & openInTextEditor
document.getElementById('show_grid').onclick = function(e) { e.stopPropagation(); showContent('show_grid'); }; // show grid
document.getElementById('show_font_grid').onclick = function(e) { e.stopPropagation(); showContent('show_font_grid'); }; // show font grid
document.getElementById('show_image_grid').onclick = function(e) { e.stopPropagation(); showContent('show_image_grid'); }; // show image grid
document.getElementById('close_audio').onclick = function(e) { e.stopPropagation(); closeMedia('audio'); }; // close audio button click
document.querySelectorAll('#prev_btn, #next_btn, .prev_next_track_btn').forEach( el => el.onclick = function(e) { clickPrevNextButtons(e,el.id); }); // ============> combine with next?
document.getElementById('directory_footer').onclick = function() { removeClass('.directory_item','hovered'); };
document.getElementById('content_image_container').querySelector('img').onclick = function(e) { scaleImages(e); focusContent('content_image_container'); }; // Zoom image on click
document.getElementById('content_title').onclick = function() { if ( getContentPaneData() !== 'has_font_file' ) { showPlaylistEntry('content_title'); } };
document.getElementById('content_audio_title').onclick = function() { if ( getContentPaneData() !== 'has_font_file' ) { showPlaylistEntry('content_audio_title'); } };
document.getElementById('play_toggle').onclick = function(e) { toggleAllChecked(e); }; // toggle media checkboxes
document.getElementById('open_playlist_label').onclick = function(e) { e.stopPropagation(); }; // Open playlist
document.getElementById('open_playlist').onchange = function(e) { e.stopPropagation(); openFile(e,'playlist'); }; // Open playlist
document.getElementById('make_playlist').onclick = function(e) { e.preventDefault(); e.stopPropagation(); closeMenus(); showWarning('warning_make_playlist'); };
document.getElementById('close_playlist').onclick = function(e) { e.preventDefault(); e.stopPropagation(); showWarning('closePlaylist'); }; // close playlist/filelist
document.getElementById('close_playlist_btn_container').onclick = function(e) { e.preventDefault(); e.stopPropagation(); showWarning('closePlaylist',[true]); };// close playlist/filelist
document.querySelectorAll('textarea,div[contenteditable],select').forEach( el => el.onclick = function(e) { e.stopPropagation(); }); // prevents losing textarea focus on click
document.querySelectorAll('#content_pane .content').forEach( el => el.onclick = function() { focusContent(); }); // focus content on click
document.querySelectorAll('#text_editor, #show_text_editor').forEach( el => el.onclick = function(e) { e.preventDefault(); showContent( el.id ); }); // set up and show top level text editor
document.querySelectorAll('#increase,#decrease').forEach( el => el.onclick = function(e) { e.stopPropagation(); scaleButtons(e,el.id); }); // click scale buttons
document.querySelectorAll('#warnings_container button').forEach( el => el.onclick = function(e) { e.preventDefault(); e.stopPropagation(); warningButtons( this.id ); });
document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onclick = function(e) { e.preventDefault(); e.stopPropagation(); return; }); // prevent user actions with warning or overlay
document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onmousedown = function(e) { e.preventDefault(); e.stopPropagation(); return; }); // prevent user actions with warning or overlay
document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onmouseup = function(e) { e.preventDefault(); e.stopPropagation(); return; }); // prevent user actions with warning or overlay
// HOVER EVENTS
document.querySelectorAll('#menu li').forEach( el => el.onmouseenter = function() {
if ( document.querySelector('#menu li.selected') !== null ) { removeClass('#menu li','selected'); addClass('#menu li:hover','selected'); } // hide open submenus, and add selected class to hovered menu items
//if ( document.querySelector('#menu li.has_open_submenu') !== null ) { removeClass('#menu li.has_open_submenu,#menu .hovered','has_open_submenu hovered'); } // hide open submenus, and add selected class to hovered menu items
});
document.querySelector('#cue_sheet_track_list_container_audio').onmouseenter = function() {
let el = document.getElementById('cue_sheet_track_list_container_audio');
document.querySelector('#cue_sheet_track_list_container_audio > div').style.top = el.offsetTop + el.clientHeight - 4 +'px'; // set y position of cuesheet track list
};
document.getElementById('footer_utilities').onmouseenter = function() { addClass('body:not(.has_menu), body:not(.has_menu_parents)','faded'); };
document.getElementById('footer_utilities').onmouseleave = function() { removeClass('body:not(.has_menu), body:not(.has_menu_parents)','faded'); };
document.querySelectorAll('#audio,#content_video').forEach( el => el.onended = function() { arrowKeyNavigation(['ArrowRight',true]); });
document.querySelectorAll('#loop_media_files,#shuffle_media_files').forEach( el => el.onclick = function() { audioPlaybackOptions( el.id ); closeMenus(); }); // media option menu items
document.querySelectorAll('#audio_options input').forEach( el => el.onclick = function() { audioPlaybackOptions( el.id ); el.blur(); document.getElementById('top').focus(); });
// document.getElementById('audio').ontimeupdate = getMediaTimeRemaining; // update remaining media time; !!!disabled until remainging time fixed
initDirListEventListeners(); // init dir list event listeners
}
function initBaseEventListeners() { // ===> INIT BASE EVENT LISTENERS: minimal listeners needed for error pages
window.addEventListener('message',receiveMessage,false); // init receive messages
document.onclick = function(e) { closeMenus(e); }; // close menu click
// document.querySelectorAll('#directory_menus a').forEach( el => el.onclick = function(e) { e.preventPropagation(); showMenus('parents_dir_menu'); });
document.querySelectorAll('.toggle_UI_pref').forEach( el => el.onclick = function(e) { toggleUIPrefOnClick(e,this.id); }); // toggle UI prefs click
document.getElementById('directory_wrapper').onclick = focusSidebar; // focus sidebar on various clicks
document.getElementById('parents_dir_menu').onclick = function(e) { e.stopPropagation(); showMenus('parents_dir_menu'); }; // show menus click
document.querySelector('#parent_dir_nav a').onclick = function(e) { e.preventDefault(); changeLocation(this.href); }; // parents menu items
document.getElementById('menu_container').onclick = function(e) { e.stopPropagation(); showMenus('menu_container'); }; // show menus click
document.getElementById('show_help').onclick = function(e) { e.stopPropagation(); addClass('body#top','has_help'); removeClass('body','has_menu'); }; // show help click
document.getElementById('close_help').onclick = function(e) { e.preventDefault(); removeClass('body','has_help'); }; // close help click
document.getElementById('help_container').onclick = function(e) { e.stopPropagation(); }; // help container: do nothing for clicks
document.getElementById('close_btn').onclick = function(e) { e.stopPropagation(); e.preventDefault(); closeButton(); this.blur(); }; // close button
document.getElementById('reload_btn').onclick = function(e) { e.stopPropagation(); e.preventDefault(); reloadContent(); this.blur(); this.classList.remove('reset'); }; // reset button
document.getElementById('handle').onmousedown = resizeSidebar; // resize sidebar
document.addEventListener('mouseup',function(e) { document.onmousemove = null; }); // revoke drag on mouseup
}
function initDirListEventListeners() { // ===> INIT DIR_LIST EVENT LISTENERS; called whenever new dir list items added
if ( !isTopWindow() ) { return; }
if ( document.querySelector('.directory_item') !== null ) { document.querySelectorAll('.directory_item').forEach( el => el.onclick = function(e) { clickDirListItem(e,el.id); }); } // show item or play/pause media
if ( document.querySelector('.directory_item.dir') !== null ) { // open/close subdirectories
document.querySelectorAll('.directory_item.dir .has_icon_before_before').forEach( el => el.onclick = function(e) { openCloseSubdirectory(e,el.closest('.directory_item').id); });
document.querySelectorAll('.directory_item.dir').forEach( el => el.ondblclick = function(e) { e.preventDefault(); showWarning('doubleClickThis', [el.id, el.querySelector('a').href] ); });
}
if ( document.querySelector('.directory_item.link') !== null ) { // open link files on dblclick
document.querySelectorAll('.directory_item.link').forEach( el => el.ondblclick = function(e) { openLinkFile(e); });
}
if ( document.querySelector('.directory_item.media') !== null ) { // Click media checkboxes
document.querySelectorAll('.directory_item.media input').forEach( el => el.onclick = function(e) { toggleChecked(e,el.closest('.directory_item').id); });
}
if ( document.querySelector('.directory_item.playlist') !== null ) {
document.querySelectorAll('.directory_item.playlist').forEach( el => el.ondblclick = function(e) { e.preventDefault(); e.stopPropagation(); document.getElementById('open_playlist').click(); });
}
if ( document.querySelector('#show_invisibles_container') !== null ) { document.querySelector('#show_invisibles_container').onclick = function(e) {
e.stopPropagation(); document.querySelector('#show_invisibles_container input').click(); }
}
}
function initStatsEventListeners() { // ===> INIT STATS EVENT LISTENERS
if (document.getElementById('stats_summary') !== null ) { document.getElementById('stats_summary').onclick = function(e) { e.stopPropagation(); showStats(); } } // show stats
document.querySelectorAll('#stats_details li, #stats_summary_detailed_dirs, #stats_summary_detailed_files').forEach( el => el.onmouseenter = function() {
document.querySelectorAll('.directory_item'+ getHoveredStatsListClass(el)).forEach( el => el.classList.add('hovered')); // add the hovered class
if ( document.querySelector('.directory_item.hovered') !== null ) {
document.querySelector('.directory_item.hovered').scrollIntoView({ behavior:'smooth',block:'nearest',inline:'nearest' }); } // scroll 1st matched el
});
document.querySelectorAll('#stats_details li, #stats_summary_detailed_dirs, #stats_summary_detailed_files').forEach( el => el.onmouseleave = function() { removeClass('.directory_item.hovered','hovered'); });
document.querySelectorAll('#stats_details li, #stats_summary_detailed_dirs, #stats_summary_detailed_files').forEach( el => el.onclick = function() { // onclick stats footer detail items
if ( !hasClass('body','sort_by_kind') ) { document.getElementById('sort_by_kind').click(); } // sort by kind
if ( document.querySelector('.directory_item'+ getHoveredStatsListClass(el)) !== null ) {
if ( el.classList.contains('invisible') && getSearchParam('show_invisibles') === 'false' ) { document.getElementById('show_invisibles').click(); }
if ( el.classList.contains('ignored') && getSearchParam('hide_ignored_items') === 'true' ) { document.getElementById('hide_ignored_items').click(); }
showThis(document.querySelector('.directory_item'+ getHoveredStatsListClass(el)).id); // click the first matched dir_list item
}
});
}
function initGridItemEventListeners() { // ===> INIT GRID ITEM EVENT LISTENERS
document.querySelectorAll('#content_grid div').forEach( el => el.onclick = function(e) { showGridItem(e,el.dataset.id,el.querySelector('a').href,el.dataset.kind); }); // add event watchers
document.querySelectorAll('#content_grid div:not(.selected)').forEach( el => el.onmouseenter = function() { addClass('#'+el.dataset.id,'hovered'); });
document.querySelectorAll('#content_grid div:not(.selected)').forEach( el => el.onmouseleave = function() { removeClass('#'+el.dataset.id,'hovered'); });
document.querySelectorAll('.directory_item.image,.directory_item.font').forEach( el => el.onmouseenter = function() { if ( getContentPaneData() === 'has_grid' ) { addClass('#content_grid > div[data-id="'+ el.id +'"]','hovered'); } });
document.querySelectorAll('.directory_item.image,.directory_item.font').forEach( el => el.onmouseleave = function() { if ( getContentPaneData() === 'has_grid' ) { removeClass('#content_grid > div[data-id="'+ el.id +'"]','hovered'); } });
}
function initCueSheetListEventListeners() { // ===> INIT CUESHEET EVENT LISTENERS
document.querySelectorAll('.cue_sheet_track_list_container li').forEach( el => el.onclick = function(e) {
e.stopPropagation();
let $parent_nav_id = el.closest('nav').id;
switch(true) {
case el.classList.contains('selected'): playPauseMedia(); break;
default:
addRemoveClassSiblings('#'+ this.id,'selected');
let time = this.getAttribute('data-duration'); // N.B.: cue time format is mm:ss:ff (ff = frames, 75 frames/sec)
switch($parent_nav_id) {
case 'cue_sheet_track_list_container_audio': if ( time < document.getElementById('audio').duration ) { document.getElementById('audio').currentTime = time; } break;
case 'cue_sheet_track_list_container_video': if ( time < document.getElementById('content_video').duration ) { document.getElementById('content_video').currentTime = time; } break;
}
}
});
}
//============================//
// INITIALIZE IFRAME EVENT LISTENERS
function initIframeEventListeners() { // ===> INIT IFRAME EVENT LISTENERS
if ( isTopWindow() ) { return; }
if ( document.getElementById('open_in_sidebar') !== null ) {
document.getElementById('open_in_sidebar').onclick = function(e) { e.preventDefault(); sendMessage('top','open_iframe_dir_in_sidebar','',window.location.href); };
}
if ( document.querySelector('.directory_item.dir') !== null ) {
document.querySelectorAll('.directory_item.dir .has_icon_before_before').forEach( el => el.onclick = function(e) { openCloseSubdirectory(e,el.closest('.directory_item').id); }); // open/close iframe subdirs on click
document.querySelectorAll('.directory_item.dir').forEach( el => el.ondblclick = function(e) { e.preventDefault(); showWarning('doubleClickThis', [el.id, el.querySelector('a').href] ); }); // open iframe dirs on dbl
}
if ( document.querySelector('.directory_item') !== null ) { document.querySelectorAll('.directory_item').forEach( el => el.onclick = function(e) { iframeDirectoryClick(e,el.id); }); } // select iframe items
document.onclick = function() { removeClass('#iframe_body','has_stats'); }
document.querySelectorAll('#iframe_body, #iframe_body #directory_list').forEach( el => el.onclick = function() {
removeClass('body','is_blurred'); sendMessage('top','iframe_click'); // tell top to close menus, focus content
});
if ( document.querySelector('.directory_item.file:not(.ignored)') !== null ) {
document.querySelectorAll('.directory_item.file:not(.ignored) a').forEach( el => el.ondblclick = function(e) { iframedoubleClickThis( e,this.closest('.directory_item').id, this.href ); // open iframe files on dblclick
});
}
if ( document.querySelector('#iframe_parent_link') !== null ) { document.getElementById('iframe_parent_link').onclick = function(e) { iframeClickLink(e,'','iframe_parent_link') } } // iframe parent
document.getElementById('show_grid').onclick = function(e) { e.stopPropagation(); showContent('show_grid'); }; // show grid
document.querySelectorAll('.toggle_UI_pref').forEach( el => el.onclick = function(e) { toggleUIPrefOnClick(e,el.id); }); // toggle UI prefs
window.addEventListener("message", receiveMessage, true); // initialize receive messages
initStatsEventListeners(); // initialize stats events listeners
// parent directory link for source directory view
//document.querySelectorAll('#parentDirText,#UI_goUp a.up').forEach( el => el.onclick = function(e) {
// e.preventDefault(); e.stopPropagation(); removeClass('#iframe_body','has_stats');
// if ( window.location.search.indexOf('view_directory_source') > -1 ) { window.location = (el.closest('a').href + '?&view_directory_source=true'); }
//});
}
function initTextEditorEventListeners() { // ===> INIT TEXT EDITOR EVENT LISTENERS
document.querySelectorAll('#iframe_body, #iframe_body #toolbar, #iframe_body #toolbar li').forEach( el => el.onclick = function() {
removeClass('body','is_blurred'); sendMessage('top','iframe_click'); // tell top to close menus, focus content
});
document.querySelectorAll('#toolbar_buttons .toggle_UI_pref').forEach( el => el.onclick = function(e) { toggleUIPrefOnClick(e,el.id); }); // text editing UI is not in DOM on page load;
document.querySelectorAll('#iframe_body textarea,#iframe_body form,#iframe_body select,#iframe_body input,#iframe_body option').forEach( el => el.onclick = function(e) {
e.preventDefault(); e.stopImmediatePropagation(); // needed to allow elements to be selected
});
let $preview = document.getElementById('text_editor_preview_pane');
// Toolbar button functions document.onmouseup = function() { null; focusTextEditorPanes(); };
document.getElementById('toolbar').onmousedown = function(e) { e.preventDefault(); }; // prevent textarea from losing focus when clicking sidebar
document.getElementById('text_editing_handle').onmousedown = function(e) { e.stopPropagation(); MDresizeSplit(); }; // resize text editor panes
document.getElementById('text_editing_handle').onmouseup = function() { document.onmousemove = null; focusTextEditorPanes(); }; // resize text editor panes
document.getElementById('text_editing_handle').ondblclick = function(e) { // reset editor elements to 50/50 split
e.stopPropagation(); document.querySelectorAll('#text_editor_raw_pane,#text_editor_preview_pane,#text_editor_html_pane,#text_editing_handle').forEach( el => el.removeAttribute('style') );
};
window.onresize = function() {
document.querySelectorAll('#text_editor_raw_pane,#text_editor_preview_pane,#text_editor_html_pane,#text_editing_handle').forEach( el => el.removeAttribute('style') ); // reset split on window resize;
};
// document.querySelectorAll('label').forEach( el => el.onclick = function(e) { el.parentElement.querySelector('input').click(); }); // Click labels to toggle checkboxes (text_editor_preview, toolbar)
// Sync scroll // convert this so that function is triggered only by hovered element, not when targeted el is scrolled
document.getElementById('text_editor_raw_pane').oninput = function() { // on input...
if ( !hasClass('body','edited') ) { addClass('body','edited'); if ( !isTopWindow() ) { sendMessage('top','iframe_edited','',''); } } // add edited body class, and if iframe, send edited message to top
MDlivePreview(); // update live preview
};
document.getElementById('text_editor_raw_pane').onscroll = function(e) { MDsyncScroll(e,'text_editor_raw_pane'); };
document.getElementById('text_editor_html_pane').onscroll = function(e) { MDsyncScroll(e,'text_editor_html_pane'); };
// $preview.on('scroll'): see "scroll_iframe" in "scroll_script" added to iframe src_doc
document.querySelectorAll('#save_btn li').forEach( el => el.onclick = function() { saveBtn(el.id); }); // save text editor content
document.getElementById('clear_text').onclick = function() { showWarning('clearText'); }; // clear text button
$preview.querySelectorAll('.checklist input').forEach( el => el.onclick = function(e) { e.stopPropagation(); MDliveCheckBoxes(el); });// Live checkboxes
$preview.querySelectorAll('.table-of-contents a').forEach( el => el.onclick = function(e) { e.preventDefault(); MDtocClick(el.id); }); // Preview TOC click navigation
$preview.querySelectorAll('.uplink').forEach( el => el.onclick = function(e) { e.stopPropagation(); MDheaderClick(); }); // Click header uplinks
document.querySelectorAll('#warnings_container button').forEach( el => el.onclick = function(e) { e.preventDefault(); e.stopPropagation(); warningButtons( this.id ); });
document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onclick = function(e) { e.preventDefault(); e.stopPropagation(); return; }); // prevent user actions with warning or overlay
document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onmousedown = function(e) { e.preventDefault(); e.stopPropagation(); return; }); // prevent user actions with warning or overlay
document.querySelectorAll('body.has_overlay, body.has_warning').forEach( el => el.onmouseup = function(e) { e.preventDefault(); e.stopPropagation(); return; }); // prevent user actions with warning or overlay
}
function initIframeHtmlFileEventListeners() { // ===> INIT IFRAME HTML FILE EVENT LISTENERS
if ( document.querySelector('#iframe_body a') !== null ) { document.querySelectorAll('#iframe_body a').forEach( el => el.onclick = function(e) { iframeClickLink( e, el.getAttribute('href'), '' ); }); }
}
function initFontPreviewEventListeners() { // ===> INIT FONT PREVIEW EVENT LISTENERS
document.querySelectorAll('#font_toolbar,#font_specimen_grid,#specimen_glyph').forEach( el => el.onclick = function(e) { e.stopPropagation(); focusContent(); }); // Stop propagation
document.getElementById('unicode_char_ranges_select').onchange = function(e) { e.preventDefault(); e.stopPropagation(); unicodeChars(this.value); document.getElementById('font_specimen').scroll(0,0); } // unicode chars
document.getElementById('font_variant_select').onchange = function(e) { e.preventDefault(); e.stopPropagation(); setFontFeatureSettings(this.value,this.id); } // set font-variant
document.getElementById('font_stylistic_set_select').onchange = function(e) { e.preventDefault(); e.stopPropagation(); setFontFeatureSettings(this.value,this.id); } // set font-variant
document.getElementById('font_tag_textarea').oninput = function(e) { e.stopPropagation(); setFontFeatureSettings(this.value,this.id); } // set custom font-feature-settings
}
//============================//
// INITIALIZE KEYDOWN EVENTS
document.querySelectorAll('#top, #iframe_body').forEach( el => el.onkeydown = function(e) {
const $selected = ( getContentPaneData() === 'has_font_file' ? document.querySelectorAll('.glyph_container.selected .glyph')[0] : document.querySelectorAll('.directory_item.selected')[0] );
switch(true) { // Disable all keydown events except escape, return, and tab when help or warning is shown, or when textarea is focused
case document.activeElement.hasAttribute('contentEditable') && !/escape|tab/.test(e.key.toLowerCase()) && !( cmdKey(e) && e.key === 'w'):
case ( /button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ) && !(cmdKey(e) && (e.key === 'r' || e.key === 'w') ) && !/escape|tab|shiftkey|metakey|altkey/.test(e.key.toLowerCase()) ):
case (hasClass('body','has_warning') || hasClass('body','has_help') ) && !( cmdKey(e) || (/escape|tab|shiftkey|enter/.test(e.key.toLowerCase()) ) ):
return;
}
// MAIN KEYDOWN EVENTS
if ( e.key && !e.metaKey && !e.altKey && !e.ctrlKey && !/ArrowLeft|ArrowRight|ArrowUp|ArrowDown/.test(e.key) ) { alphaNav(e); } // alphanumeric navigation
switch(true) { // else alphanumeric + modifier keys
case e.key === 'shiftKey' && ( hasClass('body','has_warning') || hasClass('body','has_help') ): // Shift Key
if (e.key !== 'Enter' && e.key !== 'Tab') { e.preventDefault(); return false; } break;
case ( /ArrowLeft|ArrowRight|ArrowUp|ArrowDown/.test(e.key) ): { arrowKeyFunctions(e,false,el); } break; // arrowKeyFunctions(e,bool,selected_el.id); id for dblclick iframe items
case e.key === ' ': // Key = Space
switch(true) { // Play/pause media (space bar)
case hasClass('#content_pane','has_audio') || getContentPaneData() === 'has_video':
e.preventDefault(); e.stopPropagation(); playPauseMedia(); break;
case !isTopWindow() && document.querySelector('.directory_item.audio_loaded') !== null:
e.preventDefault(); e.stopPropagation(); sendMessage('top','iframe_play_pause_media'); break;
}
break;
case e.key === 'Enter': // Key = Enter/Return
switch(true) {
case hasClass('body','focus_content') && /has_font/.test(getContentPaneData()) && document.activeElement.contentEditable !== true && document.querySelector('#content_font .selected') !== null:
if ( /has_font|has_glyph/.test(getContentPaneData() ) ) { showFontGlyph( document.querySelector('#content_font .selected').id); }
break; // show selected font specimen glyph
case hasClass('body','has_warning') || hasClass('body','has_help'): e.preventDefault();
if (document.querySelectorAll('button.focus, button:focus') !== null ) { document.querySelectorAll('button.focus, button:focus')[0].click(); } // click focused warning button in #top or #iframe
break;
case !isTopWindow(): // if iframe...
switch(true) {
case hasClass('body','has_menu'): sendMessage('top','clickMenu'); break; // close main menu
case document.querySelector('.directory_item.audio.selected') !== null && !hasClass('.directory_item.audio.selected','audio_loaded'): console.log("B");
iframedoubleClickThis(e,document.querySelector('.directory_item.selected').id,document.querySelector('.directory_item.selected a').href)
break;
case document.querySelector('.directory_item.selected') !== null && cmdKey(e):
iframedoubleClickThis(e,document.querySelector('.directory_item.selected').id,document.querySelector('.directory_item.selected a').href)
break; // webloc or url file
case document.querySelector('.directory_item.audio_loaded') !== null && !hasClass('.directory_item.selected','audio_loaded'):
e.preventDefault(); e.stopPropagation(); playPauseMedia(); break; // play/pause media
}
break;
case hasClass('body','has_menu'): e.preventDefault(); clickMenu(); sendMessage('iframe','close_menu'); break; // click selected menu item
case $selected.classList.contains('app') && $settings.apps_as_dirs === false: break; // don't open app folders
default:
switch(true) {
case $selected.classList.contains('.disabled'): case getContentPaneData() === 'has_text_editor': break; // no nothing for disabled or default behavior
case $selected.classList.contains('audio') && !$selected.classList.contains('audio_loaded'):
showAudio( document.querySelector('.directory_item.audio.selected').id ); break; // show selected audio file
case $selected.classList.contains('media'): e.preventDefault(); e.stopPropagation(); playPauseMedia(); break; // else play/pause playing media
case ( /dir|link|playlist/.test($selected.classList) ) && cmdKey(e):
doubleClickThis(e,document.querySelector('.directory_item.selected').id,document.querySelector('.directory_item.selected a').href)
// $selected.querySelector('a').dblclick();
break; // open dirs, links, playlists
case ( /dir|link/.test($selected.classList) ): // nobreak
default: $selected.click(); // default: click selected
}
}
break;
case e.key === 'd' && cmdKey(e) && !hasClass('body','has_warning'): e.preventDefault(); document.querySelector('#show_details').click(); break; // Cmd/Ctrl + D: Toggle Details
case e.key === 'e': // Cmd/Ctrl + E: Toggle Main Menu or Text Editor
switch(true) {
case hasClass('body','has_warning'): break;
case cmdShiftKey(e):
e.preventDefault();
if ( !isTopWindow() ) { sendMessage('top','toggle_text_editor'); } else { document.querySelector('#show_text_editor a').click(); }
addClass('body','faded');
break; // toggle text editor
case cmdKey(e): e.preventDefault(); if ( !isTopWindow() ) { sendMessage('top','toggle_menu'); } else { showMenus('menu_container'); } break; // toggle main menu
}
break;
case e.key === 'g' && cmdKey(e) && ( hasClass('#top','has_images') || hasClass('#top','has_fonts') ): e.preventDefault(); document.querySelector('#show_grid').click(); break; // Cmd/Ctrl + G: Show image Grid
case e.key === 'i' && cmdKey(e): if ( !isTopWindow() ) { sendMessage('top','toggle_invisibles'); } else { document.querySelector('#show_invisibles_container input').click(); } break; // toggle invisibles
case e.key === 'o' && cmdShiftKey(e): window.open( getAttribute('.directory_item.selected a','href') ); break; // Cmd/Ctrl + Shift + O: Open selected item in new window
case e.key === 'r': // Cmd/Ctrl + Shift + R: Refresh
switch(true) {
case cmdKey(e) && hasClass('#iframe_body','edited'): e.preventDefault(); showWarning('reloadContent'); break; // warn before reloading edited iframe text files from textarea
case cmdKey(e) && !isTopWindow(): e.preventDefault(); sendMessage('top','reload'); break; // send reload message to top
case cmdKey(e): if ( getContentPaneData() === 'has_null' && !hasClass('#content_pane','has_audio') ) { return true; } // reload window if no content open...
else { e.preventDefault(); document.getElementById('reload_btn').click(); } break; // else click reload/reset button
}
break;
case e.key === 'w' && cmdKey(e): // KEY = W && Cmd/Ctrl: close content
switch(true) {
case hasClass('#iframe_body','edited'): e.preventDefault(); showWarning('closeContent'); break; // warn before closing edited iframe text files from textarea
case !isTopWindow(): e.preventDefault(); sendMessage('top','close'); break; // send close message to top
case hasClass('#content_pane','has_audio') && getContentPaneData() === 'has_null': e.preventDefault(); closeMedia('audio'); break; // close audio
case hasClass('body','has_playlist'): case hasClass('body','has_filelist'): // nobreak; close playlists/filelists
case hasClass('body','iframe_edited'): // nobreak; close edited iframe file
case getContentPaneData() !== 'has_null': e.preventDefault(); document.getElementById('close_btn').click(); break; // click close button
default: return true; // else close window (or normal behavior)
}
break;
case e.key === '=' && cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test(getContentPaneData()): e.preventDefault(); scaleButtons(e,'increase'); break; // Cmd/Ctrl + equals sign: scale larger
case e.key === '-' && cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test(getContentPaneData()): e.preventDefault(); scaleButtons(e,'decrease'); break; // Cmd/Ctrl + hyphen: scale smaller
case e.key === '\\': // KEY = \ BACKSLASH
switch(true) {
case cmdShiftKey(e): // Cmd Shift + \ : toggle split
switch(true) {
case isTopWindow() && getContentPaneData() === 'has_iframe': sendMessage('iframe','text_editor_split_view'); break; // send toggle split message to top
case document.querySelector('#text_editor_split_view').height > 0: document.querySelector('#text_editor_split_view').click(); break; // if split view visible...click toggle split
}
break;
case cmdKey(e): // Cmd + \ : toggle sidebar
if ( !isTopWindow() ) { sendMessage('top','hide_sidebar'); } else { document.querySelector('#hide_sidebar').click(); }
break;
}
break;
case e.key === 'Tab': // KEY = TAB
e.preventDefault();
switch(true) {
case hasClass('body','has_warning'): navigateWarningButtons(e); break; // focus warning buttons
case document.activeElement.id === 'content_image': focusSidebar(); break; // focus sidebar from image
case getContentPaneData() === 'has_text_editor': focusTextEditorPanes(); break; // focus text editor
case getContentPaneData() === 'has_font' && document.activeElement.classList.contains('specimen'):
case !isTopWindow() && /\.html|\.htm/.test(window.location.pathname) && document.querySelector('a,button,input,select,textarea,div[contenteditable]') !== null:
focusFocusableEls(e); break;
case (/has_markdown|has_text|has_code|has_htm/.test(getContentPaneData()) && isTopWindow() ):
addClass('body','focus_content'); document.getElementById('content_iframe').focus();
sendMessage('iframe','focus_iframe'); break; // focus iframe text editor or htm files from top
case ( /text_editor_preview_pane|text_editor_html_pane|text_editor_raw_pane/.test(document.activeElement.id) ): toggleTextEditorPanes(); break; // toggle focused text editor panes
case document.activeElement.closest('#text_editor_preview_pane') !== null: focusFocusableEls(e,'#text_editor_preview_pane'); break; // focus focusable elements in text preview
case document.activeElement.closest('#font_specimen') !== null: focusFocusableEls(e,'#font_specimen'); break; // focus focusable elements in font specimen
// case document.activeElement.hasAttribute('contenteditable'):
case !isTopWindow(): // If iframe is focused, focus sidebar, dim iframe dir_list.selected
e.preventDefault();
switch(true) {
case document.querySelector('#directory_wrapper.iframe') !== null:
case !/a|button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ): // allow form elements to be focused
addClass('#iframe_body,.directory_item.selected','is_blurred'); // dim iframe dir_list
sendMessage('top','tab'); break; // focus sidebar
case document.querySelector('a,button,input,select,textarea') !== null: // allow tabbing to form elements and textareas (e.g., in html pages)
if ( hasClass('body','has_text_editor_UI') ) { focusFocusableEls(e,'#text_container'); } else { focusFocusableEls(e); }
break;
}
break;
case isTopWindow(): // Either focus sidebar or refocus content pane (after clicking menu, for example)
switch(true) {
case hasClass('body','focus_content') && hasClass('body','has_menu'): case !hasClass('body','focus_content'): focusContent('',e); break;
case hasClass('body','focus_content'): case hasClass('body','has_menu'): focusSidebar(); break;
}
break;
}
break;
case e.key === 'Escape': // remove text selections, close warnings or help, close menus (and refocus content), or focus sidebar.
if ( getAttribute('#content_pane','data-loaded') !== 'loaded' ) { removeAttribute('#content_pane',['data-loaded']); removeAttribute('#content_iframe',['src']); } // close loading iframe
if ( !isTopWindow() ) { addClass('#iframe_body','is_blurred'); sendMessage('top','escape'); } else { focusSidebar(); }
window.getSelection().removeAllRanges();
removeClass('#content_playlist, #content_audio_playlist','has_content');
if ( document.querySelector('#warning_btn_cancel,#close_help') !== null ) { document.querySelector('#warning_btn_cancel,#close_help').click(); }
document.onmousemove = null;
removeClass('body','has_overlay');
break;
case e.key === '.' && cmdKey(e): if ( hasClass('body','has_warning') ) { e.preventDefault(); document.querySelector('#warning_btn_cancel,#close_help').click(); } break; // click cancel button
} // end switch
});
// ***** END EVENT LISTENER INITIALIZATION
//============================//
function setLocation(link) { window.location = link; }
function changeLocation(args) { // args[0] === href, args[1] === 'external'
switch(true) {
case args[0][1] === 'external': window.open(args[0][0]); break; // open external menu links: about, coffee, contact
case hasClass('body','has_playlist'): case hasClass('body','has_filelist'): showWarning('closePlaylist',[true,args[0]]); break; // show playlist warning
case ( /^file:\/\//.test(args[0]) && $protocol !== 'file:'): addClass('body','has_warning'); addClass('#warnings_container','warning_local_bookmark'); document.getElementById('warning_btn_ok').focus(); break;
default: showWarning( 'setLocation',args.toString() );
}
}
function showMenus(id) { // ===> SHOW MENUS
sendMessage('iframe','top_has_menu');
removeClass('body','hide_sidebar');
if ( hasClass('body','has_stats') ) { removeClass('body','has_stats'); }
let position = document.getElementById('directory_header').offsetHeight - document.getElementById('directory_buttons').offsetHeight - document.getElementById('sorting').offsetHeight - document.getElementById('show_text_editor').offsetHeight + 1;
document.querySelector('#'+ id +' > ul').style.top = position + 'px';
switch(id) {
case 'parents_dir_menu':
switch(true) {
case hasClass('body','has_menu_parents'): removeClass('body','has_menu_parents'); break;
default: addClass('body','has_menu_parents'); removeClass('body','has_menu');
}
break;
case 'menu_container':
switch(true) {
case hasClass('body','has_menu'): removeClass('body','has_menu'); sendMessage('iframe','top_closed_menu'); break;
default: addClass('body','has_menu'); removeClass('body','has_menu_parents'); sendMessage('iframe','top_has_menu');
}
}
document.querySelectorAll('#directory_menus a').forEach( el => el.onclick = function(e) { e.stopPropagation(); e.preventDefault(); // init menu links click
switch(true) {
case ( /about_link|donate_link|contact_link/.test(el.id) ): showWarning('changeLocation',[el.href,'external']); break; // external menu links
default: showWarning('changeLocation',[el.href]); // parents menu items
}
});
}
function clickMenu() { // ===> CLICK MENU
document.querySelector('#menu .selected:not(.hovered)').querySelectorAll('a,span,label')[0].click();
Array.from(document.querySelectorAll('#menu li')).forEach(function(el) {
el.classList.remove('selected','selected_submenu_item','hovered','has_open_submenu');
});
removeClass('body','has_menu has_open_submenu');
if ( hasClass('body','focus_content') ) { sendMessage('iframe','close_menu'); document.getElementById('content_iframe').focus(); }
if ( hasClass('body','focus_content') ) { focusContent(); }
}
function closeMenus() { // ===> CLOSE MENUS
switch(true) {
// case !hasClass('body','has_menu') && !hasClass('body','has_menu_parents') && !hasClass('body','has_stats'): break; // why is this needed?
default:
removeClass('body','has_menu has_menu_parents has_open_submenu faded has_stats has_help');
removeClass('#menu .selected, #menu .hovered','selected hovered');
}
}
function showStats() { // ===> SHOW STATS
updateDurations();
addClass('body','has_stats'); removeClass('body','has_menu has_menu_parents');
document.getElementById('stats_details').style.height = document.getElementById('stats_container').height - document.getElementById('stats_summary_detailed').height - 4;
}
function getHoveredStatsListClass(el) { // ===> GET HOVERED STATS CLASS
el.classList.remove('file','not_media','media','stats_list_item','hover_bold');
let this_class = '.'+ Array.from(el.classList).join('.');
switch(true) {
case el.id === 'stats_summary_detailed_dirs': this_class = '.dir'; break;
case el.id === 'stats_summary_detailed_files': this_class = '.file'; break;
// case /_/.test(this_class): this_class = this_class.replace('_','.'); break;
case this_class === '.dir': this_class = '.dir:not(.ignored):not(.invisible):not(.app)'; break;
case this_class === '.dir.app': this_class = '.dir.app:not(.ignored):not(.invisible)'; break;
}
return this_class;
}
//============================//
function toggleUIPref(pref_ID) { // ===> TOGGLE UI PREFS: and update searchParams
let message_target = ( isTopWindow() ? 'iframe' : 'top' );
let bodyClassNames = ( message_target === 'iframe' ? document.getElementById('top').className.split(' ') : null );
pref_ID = pref_ID.toString();
if ( pref_ID === 'theme' && hasClass('body','theme_dark') ) { pref_ID = 'theme_light'; } else if ( pref_ID === 'theme' && hasClass('body','theme_light') ) { pref_ID = 'theme_dark'; }
switch(true) { // Text editor prefs
case ( /text_editor_theme_default/.test(pref_ID) ): pref_ID = 'text_editor_'+ bodyClassNames.find( el => el.startsWith('theme') ); // nobreak
case ( /text_editor_/.test(pref_ID) ): toggleTextEditorPrefs(pref_ID); if ( isTopWindow() ) { sendMessage('iframe',pref_ID); } break; // Text Editor Preferences
case ( /enable_text_editing/.test(pref_ID) ): toggleTextEditing(pref_ID); break; // why here and not in case above? // Enable/Disable Text editing
case ( /sort_by_/.test(pref_ID) ): toggleSort(pref_ID); break; // toggle sorting
case ( /theme_light|theme_dark/.test(pref_ID) ): toggleTheme(pref_ID); break; // toggle light/dark theme
case ( /show_image_thumbnails/.test(pref_ID) ): document.body.classList.toggle('show_image_thumbnails'); showImageThumbnails(); break;
default:
if ( /button|label|select|input/.test(document.activeElement.tagName.toLowerCase() ) ) { document.activeElement.blur(); document.body.focus(); } // blur any focused form elements
document.body.classList.toggle(pref_ID); // toggle body class
// toggleSearchParam
break;
}
if ( isTopWindow() ) { toggleSearchParam(pref_ID); sendMessage('iframe','toggleUIPref','',pref_ID); }
if ( /show_invisibles|hide_ignored_items/.test(pref_ID) ) { updateStats(); } // update stats if necessary
if ( isTopWindow() && document.querySelector('.directory_item.selected') !== null ) { scrollThis('#directory_list','.selected',false); } // scroll to selected dirlist item
if ( hasClass('body','focus_content') ) { focusContent(); } // focus content if necessary
}
function toggleTextEditing(pref_ID) {
document.body.classList.toggle('enable_text_editing');
document.body.classList.toggle('disable_text_editing');
if ( document.querySelectorAll('.selected.text, .selected.markdown, .selected.code')[0] !== undefined ) {
showThis( document.querySelectorAll('.selected.text, .selected.markdown, .selected.code')[0].id );
}
}
function toggleSort(pref_ID) {
let has_dir = false, sorted, sort_direction, iframe_src, iframe_url, iframe_params, items_html_arr = [];
if ( isTopWindow() && document.getElementById('content_pane').dataset.content === 'has_dir' ) { has_dir = true; iframe_src = document.getElementById('content_iframe').src; }
switch(true) {
case document.body.classList.contains(pref_ID): // if pref_ID = current sort, reverse sort direction
if ( isTopWindow() ) {
sort_direction = ( getSearchParam('sort_direction') === 'sort_ascending' ? 'sort_descending' : 'sort_ascending' ); // define new sort direction
toggleSearchParam( sort_direction );
} else {
sort_direction = ( hasClass('body','sort_ascending') ? 'sort_descending' : 'sort_ascending' ); // toggle sort direction search param
}
document.body.classList.remove('sort_descending','sort_ascending'); // remove sort direction body class
document.body.classList.add(sort_direction); break; // add new sort direction body class
default: // else if select different sort
removeClass('body','has_menu sort_by_name sort_by_default sort_by_duration sort_by_size sort_by_date sort_by_kind sort_by_ext sort_descending sort_ascending'); // remove all sorting body classes
sort_direction = $settings.sort_direction; // set default sort direction
if (isTopWindow() ) { setSearchParam('sort_direction', sort_direction); } // set default sort direction searchparam
document.body.classList.add(pref_ID,$settings.sort_direction); // add new sorting body classes
}
Array.from(document.querySelectorAll('.directory_item')).forEach(el => items_html_arr.push(el.outerHTML.replace(/border_bottom |border_top /g,''))); // get elements for new sort
sorted = sortDirList( Array.from(items_html_arr), pref_ID, sort_direction ); // sort the items
document.getElementById('directory_list').innerHTML = sorted; // insert sorted items into dir_list
initDirListEventListeners(); // re-initialize dir_list event listeners
if ( !isTopWindow() ) { initIframeEventListeners(); }
switch(true) {
case hasClass('#content_pane','has_font_grid'): showGrid('show_font_grid'); break; // sort grids --> change this to actual sort, not reload
case hasClass('#content_pane','has_image_grid'): showGrid('show_image_grid'); break; // sort grids --> change this to actual sort, not reload
case getContentPaneData() === 'has_grid': showGrid('show_grid'); break; // sort grids --> change this to actual sort, not reload
case has_dir === true: // re-sort iframe directory
showThis(document.querySelector('.directory_item.selected').id); // show the selected directory
iframe_url = new URL(iframe_src); // create url obj
iframe_params = new URLSearchParams(iframe_url.search) // create url search params
iframe_params.set('sort_by',pref_ID.slice(pref_ID.lastIndexOf('_') + 1)); // set sort_by
iframe_params.set('sort_direction',sort_direction); // set sort_direction params
iframe_url.search = iframe_params.toString(); // update url search params
document.getElementById('content_iframe').src = iframe_url.href; break; // reload the iframe with new src url
case document.querySelector('.directory_item.selected') !== null: showThis(document.querySelector('.directory_item.selected').id); break; // after sort, show selected item; don't autoloadcoverart
}
}
function toggleTheme(pref_ID) {
removeClass('body','theme_dark theme_light');
addClass('body',pref_ID);
switch(true) {
case hasClass('body','text_editor_theme_default'): removeClass('body','text_editor_theme_light text_editor_theme_dark'); addClass('body','text_editor_'+ pref_ID); break;
default: toggleUIPref('text_editor_theme');
}
if ( isTopWindow() ) { sendMessage('iframe',pref_ID); } // send message iframe
}
function toggleTextEditorPrefs(pref_ID) { // ===> TOGGLE TEXT EDITOR PREFERENCES (from menus or toolbar buttons)
let message_target = ( isTopWindow() ? 'iframe' : 'top' ), sync_scroll_input = document.querySelector('#text_editor_sync_scroll input');
switch(true) {
case ( /text_editor_theme/.test(pref_ID) ): // toggle text editor theme
switch(true) {
case pref_ID === 'text_editor_theme':
if ( document.body.classList.contains('text_editor_theme_light') ) {
document.body.classList.remove('text_editor_theme_light','text_editor_theme_default'); document.body.classList.add('text_editor_theme_dark');
} else {
document.body.classList.add('text_editor_theme_light','text_editor_theme_default'); document.body.classList.remove('text_editor_theme_dark');
}
pref_ID = ( hasClass('body','text_editor_theme_light') ? 'text_editor_theme_light' : 'text_editor_theme_dark' ); // toggle editor theme pref_ID
break;
case pref_ID === 'text_editor_theme_default':
pref_ID = ( hasClass('body','text_editor_theme_light') ? 'text_editor_theme_light' : 'text_editor_theme_dark' ); // arg sent to iframe to set editor theme
addClass('body','text_editor_theme_default'); removeClass('body','text_editor_theme_light text_editor_theme_dark');
break;
case pref_ID === 'text_editor_theme_light': addClass('body','text_editor_theme_light'); removeClass('body','text_editor_theme_default text_editor_theme_dark'); break;
case pref_ID === 'text_editor_theme_dark': addClass('body','text_editor_theme_dark'); removeClass('body','text_editor_theme_default text_editor_theme_light'); break;
}
break;
case pref_ID === 'text_editor_raw': addClass('body','text_editor_raw'); toggleUIPref('text_editor_split_view'); break; // text_editor_raw: toggle text_editor_split_view
case ( /text_editor_preview|text_editor_html/.test(pref_ID) ): // toggle text_editor_preview/text_editor_html panes
if ( hasClass('body',pref_ID) ) { toggleUIPref('text_editor_split_view'); } // toggle split, hiding text_editor_raw pane
removeClass('body','text_editor_raw text_editor_preview text_editor_html'); // remove body classes
addClass('body',pref_ID); // add pref_ID class
break;
case ( /text_editor_split_view/.test(pref_ID) ): document.body.classList.toggle(pref_ID); // text_editor_split_view
if ( hasClass('body','text_editor_split_view') ) { document.querySelector('#text_editor_raw_pane').focus(); } break; // focus #text_editor_raw
case ( /text_editor_sync_scroll/.test(pref_ID) ): document.body.classList.toggle(pref_ID); // text_editor_sync_scroll
if ( sync_scroll_input.checked === true ) { sync_scroll_input.checked = false; } else { sync_scroll_input.checked = true; }
break;
}
switch(true) { //pref_ID += '_'+ getSearchParam('text_editor_theme');
// case pref_ID === 'text_editor_theme': sendMessage('top','toggle_text_text_editor_theme','',pref_ID); break;
// case ( /text_editor_theme_default|text_editor_theme_light|text_editor_theme_dark/.test(pref_ID) ): sendMessage('iframe','text_editor_toolbar_button','',pref_ID); break;
default: sendMessage(message_target,'text_editor_toolbar_button','',pref_ID);
}
}
function toggleUIPrefOnClick(e,id) { e.stopPropagation(); closeMenus(); showWarning('toggleUIPref',document.getElementById(id).getAttribute('data-ui_pref') ); } // ===> CLICK TOGGLE UI PREF ELEMENTS
//============================//
function resizeSidebar(e) { // ===> RESIZE SIDEBAR/Content Pane
e.stopPropagation();
closeMenus();
let $directory_wrapper = document.getElementById('directory_wrapper'), startX = e.pageX, window_width = window.innerWidth, sidebar_width = $directory_wrapper.offsetWidth;
addClass('body','has_overlay'); // prevent interference from the rest of ui
document.onmousemove = function(f) {
f.stopPropagation(); f.preventDefault();
let deltaX = f.pageX - startX;
if ( f.pageX > 230 && f.pageX < window_width - 200 ) { $directory_wrapper.style.width = ( sidebar_width + deltaX ) + 'px'; }
scrollThis('#directory_list','.selected',false); // true = instant scroll
};
document.onmouseup = function() {
removeClass('body','has_overlay'); // remove the overlay
document.onmousemove = null; // remove eventlistener
setSearchParam('width',$directory_wrapper.offsetWidth); // set the sidebar width query
if ( hasClass('body','focus_content') ) { focusContent(); } // refocus content if necessary
};
}
//============================//
// ***** BASIC UI FUNCTIONS ***** //
function scrollThis(container_ID, el, bool) { // ===> SCROLL to Selected Item
let container = document.querySelector(container_ID);
if ( container.height === 0 ) { return; } // don't scroll hidden elements
let scroll_el = container.querySelector(el);
let scroll_behavior = ( ( bool !== undefined || bool === true || getContentPaneData() === 'has_grid' ) ? 'instant' : 'smooth' ); // instant allows sidebar & grid to scroll simultaneously
let scroll_block = ( hasClass('body','is_gecko') ? 'start' : 'nearest' );
if ( scroll_el !== null ) { scroll_el.scrollIntoView({ behavior:scroll_behavior, block:scroll_block, inline:'nearest' }); }
}
function mouseMove(e,id,startX,startY,elOffsetLeft,elOffsetTop) { // Init events to allow glyphs to be dragged into view
let scale = ( id === '#specimen_glyph' ? 2 : 1 );
document.querySelector(id).style.left = elOffsetLeft + (e.pageX - startX)*scale + 'px'; document.querySelector(id).style.top = elOffsetTop + (e.pageY - startY)*scale +'px';
}
//============================//
// ***** SORTING ***** //
function addSortingBorders(sorted) { // ===> ADD SORTING BORDERS
let item_kind;
for ( let i = 0; i < sorted.length - 1; i++ ) {
item_kind = sorted[i].match(/data-kind=\"\w+?\" /)[0];
if ( sorted[i + 1].indexOf(item_kind) === -1 ) { sorted[i] = sorted[i].replace(/class=\"/,'class="border_bottom '); } // add border class
}
return sorted;
}
function sortIndex(items_html_arr,sort_type,sort_direction) { // ===> SORT INDEX ITEMS
let sort_id = sort_type.split('_').reverse()[0];
const new_sort = new Intl.Collator( undefined, { numeric: true, sensitivity: 'base' } );
let sorted = [], aName, bName, aData, bData; // aLevel, bLevel, aKind, bKind;
sorted = items_html_arr.sort( (a, b) => { // sorted items
// aLevel = a.dataset.level; bLevel = b.dataset.level; // subdirectory level
if ( !/data-name/.test(a) || !/data-name/.test(b) ) { null; } else { aName = a.replace(/(.+?)data-name="([^"]+?)"(.+)/g,'$2'); bName = b.replace(/(.+?)data-name="([^"]+?)"(.+)/g,'$2'); }// get data-name
switch(true) { // aData, bData = size, date, kind, ext, time
case !( new RegExp('data-'+ sort_id) ).test(a) || !( new RegExp('data-'+ sort_id) ).test(b): break;
default: aData = a.replace( ( new RegExp( '.+?data-'+ sort_id +'="([^"]+?)".+') ),'$1' ); bData = b.replace( ( new RegExp( '.+?data-'+ sort_id +'="([^"]+?)".+') ),'$1' );
}
switch(true) { // sort 'em!
case sort_direction === 'sort_ascending': return ( new_sort.compare(aData, bData) === 0 ? new_sort.compare(aName, bName) : new_sort.compare(aData, bData) ); // A-Z
case sort_direction === 'sort_descending': return ( new_sort.compare(bData, aData) === 0 ? new_sort.compare(bName, aName) : new_sort.compare(bData, aData) ); // Z-A
}
});
return sorted; // return sorted items
}
function sortDirList(items_html_arr, sort_type, sort_direction) { // ===> SORT DIR LIST on click
let sorted = [], sort_all = items_html_arr, sort_dirs = items_html_arr.filter( item => ( /data-kind=\"dir\"/.test(item) ) ), sort_files = items_html_arr.filter( item => ( !/data-kind=\"dir\"/.test(item) ) );
switch(true) {
case ( /sort_by_size|sort_by_date/.test(sort_type) ) && (hasClass('body','has_filelist') || hasClass('body','has_filelist has_playlist')): return items_html_arr.join('\n'); // don't sort playlists by size/date
case sort_type === 'sort_by_default' || ( sort_type !== 'sort_by_name' && $settings.dirs_on_top === true ): // if sort default or by name sort with dirs_on_top)
const sorted_dirs = sortIndex( sort_dirs, sort_type, sort_direction ), sorted_files = sortIndex( sort_files, sort_type, sort_direction ); // ...sort dirs and files separately
switch(true) {
case sort_direction === 'sort_ascending': // if sort ascending...
if ( sorted_files[0] !== undefined && sorted_dirs[0] !== undefined && !/sort_by_kind|sort_by_ext/.test(sort_type) ) {
sorted_files[0] = sorted_files[0].replace(/class=\"/,'class="border_top '); // add border class
}
sorted = sorted_dirs.concat(sorted_files); // ...dirs before files
break;
default: // sort descending...
if ( sorted_dirs[0] !== undefined && !/sort_by_kind|sort_by_ext/.test(sort_type) ) { sorted_dirs[0] = sorted_dirs[0].replace(/class=\"/,'class="border_top '); } // add border class
sorted = sorted_files.concat(sorted_dirs); // ...else files before dirs
}
break;
default: sorted = sortIndex( sort_all, sort_type, sort_direction ); // other sorts (name, size, date): files and dirs together
}
if ( /sort_by_kind|sort_by_ext/.test(sort_type) ) { sorted = addSortingBorders(sorted); } // add borders for sort by kind and ext
return sorted.join('\n');
}
// ***** END BASIC UI FUNCTIONS ***** //
//============================//
// ***** CONTENT PANE ***** //
function focusSidebar() { // ===> FOCUS SIDEBAR
document.activeElement.blur();
closeMenus();
removeClass('body','faded focus_content');
document.getElementById('directory_nav').focus();
sendMessage('iframe','blur_iframe');
}
function focusContent(id,e) { // ===> FOCUS CONTENT
let activeElementID = document.activeElement.id, $content_pane_data = getContentPaneData();
document.activeElement.blur();
closeMenus();
addClass('body','focus_content');
switch(true) {
case id === 'content_iframe' && cmdKey(e) && e.key === 'ArrowDown' && isTopWindow() && hasClass('body','focus_content'):
sendMessage('iframe','iframe_arrow_navigation','','ArrowDown');
document.getElementById(id).focus();
break;
case $content_pane_data === 'has_font':
if ( document.getElementById('font_specimen_grid').children.length > 0 ) { null } else { document.getElementById('specimen').focus(); } break;
case activeElementID.toLowerCase() === 'text_editor_preview_pane' && getFocusableEls('#text_editor_preview_pane').length > 0: getFocusableEls('#text_editor_preview_pane')[0].focus(); break;
case $content_pane_data === 'has_text_editor': focusTextEditorPanes(); break; // focus text editor
case ( /has_text|has_markdown|has_code|has_htm|has_dir|has_app/.test( $content_pane_data ) ): // no break; focus iframe text editor
case hasClass('#content_iframe','has_content'): // focus iframe and/or iframe text editor
document.getElementById('content_iframe').focus(); if ( e !== undefined && e.shiftKey ) { sendMessage('iframe','shift_focus_iframe'); } else { sendMessage('iframe','focus_iframe'); } break;
case ( /has_video|has_pdf/.test( $content_pane_data ) ): // no break; video, pdf
case $content_pane_data === undefined: // no break; data-content undefined
case hasClass('body','has_playlist'): removeClass('body','focus_content'); break; // don't focus content in this and the above two cases
case ( /has_font_file|has_glyph/.test($content_pane_data) ): document.getElementById('content_font').focus(); break; // font files and glyphs
case $content_pane_data === 'has_grid': document.getElementById('content_grid').focus(); break; // grids
case $content_pane_data === 'has_image': document.getElementById('content_image').focus(); break; // images
case id !== undefined: document.getElementById(id).focus(); break;
}
}
function getFocusableEls(id) { // ===> GET FOCUSABLE ELEMENTS
let focusableEls = ( id !== undefined ? Array.from(document.getElementById(id).querySelectorAll('a,button,input,select,textarea,div[contenteditable]')).filter( item => item.offsetWidth > 0 ) : Array.from(document.querySelectorAll('a,button,input,select,textarea,div[contenteditable]')).filter( item => item.offsetWidth > 0 ) );
return focusableEls;
}
function focusFocusableEls(e,id) { // ===> FOCUS FOCUSABLE ELEMENTS (iframe files or text editor preview)
e.preventDefault();
let els = getFocusableEls(id);
switch(true) {
case e.shiftKey: // tab + shift: focus prev
switch(true) {
case els.indexOf(document.activeElement) === 0: sendMessage('top','focus_sidebar'); break;
case !els.includes(document.activeElement): els[els.length - 1].focus(); break; // if nothing focused...focus last el
default: els[els.indexOf(document.activeElement) - 1].focus(); // else focus previous
}
break;
case document.activeElement === els[els.length - 1]:
if ( !isTopWindow() ) { sendMessage('top','focus_sidebar'); } else { focusSidebar(); } break; // if last focussable el, focus sidebar
default: els[els.indexOf(document.activeElement) + 1].focus(); // tab: select next focusable element
}
}
function focusButton(id) { let el = document.getElementById(id); el.classList.add('focus'); el.focus(); } // ===> FOCUS BUTTON (for warnings)
//============================//
// ===> SHOW INDIVIDUAL CONTENT TYPES
//============================// MEDIA
function showAudio(id,iframe_audio_link,bool) { // ===> SHOW AUDIO; bool from
closeMedia('video');
document.activeElement.blur();
document.body.focus();
let link, title;
switch(true) {
case id === 'content_iframe_file': // clicked iframe audio files
link = decodeURIComponentSafe(iframe_audio_link);
title = link.slice(link.lastIndexOf('/') + 1);
addClass('#content_pane','has_audio has_iframe_audio');
setAttribute('#content_iframe_utility','src',link.slice(0,-4)); // load cue sheet in utility iframe
break;
default: // dir_list audio files
switch(true) {
case hasClass('#'+ id,'local'): break;
case bool === 'true': // bool !== undefined: if from autoLoadFile, just select file (don't add .audio_loaded class)
if ( document.querySelector('.dir.content_loaded') !== undefined ) { addRemoveClassSiblings('.dir.content_loaded','selected'); } // select dir.selected instead of media
addClass('#'+ id,'selected');
break;
default: addRemoveClassSiblings('#'+ id,'audio_loaded selected'); // otherwise select loaded media
}
link = document.getElementById(id).querySelector('a').getAttribute('href');
title = document.getElementById(id).querySelector('a').innerText;
addClass('#content_pane','has_audio'); removeClass('#content_pane','has_iframe_audio');
getCueSheet(id,link,'audio');
}
setAttribute('#audio','src', link );
document.querySelector('#content_audio_title span').innerText = title;
removeClass('#content_audio_playlist','has_content');
}
function showVideo(id,link) { closeMedia('audio'); getCueSheet(id,link,'video'); document.activeElement.blur(); document.getElementById('content_video').focus(); } // ===> SHOW VIDEO; rest of processing is done in showContent();
//============================// MEDIA PLAYBACK
function updateTrackList() { // ===> UPDATE TRACKLIST (for shuffle play)
let playlist = [];
Array.from(document.querySelectorAll('.directory_item.media:not(.unchecked,.disabled,.audio_loaded,.content_loaded,.selected)')).forEach( el => playlist.push( el.id ) ); // don't include currently selected item
return playlist;
}
function shuffleArray(array) { // ===> SHUFFLE ARRAY: Randomize Shuffle List
for ( let i = array.length - 1; i > 0; i-- ) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array;
}
function updateShuffleList(id) { // ===> UPDATE SHUFFLE LIST
let shuffle_list;
switch(true) {
case !hasClass('body','shuffle_media'): return; // don't shuffle if normal playback
case id !== undefined: // handle checked and unchecked media items: id = checked/unchecked item.id
shuffle_list = document.getElementById('content_audio').dataset.shufflelist.split(','); // get the dataset.shufflelist and convert to array
switch(true) {
case hasClass('#'+ id,'unchecked') || ( /audio_loaded|content_loaded/.test(document.getElementById(id).classList.value) ): // remove items unchecked since shuffle option first selected
shuffle_list.splice(shuffle_list.indexOf(id), 1); break;
default: shuffle_list.push(id); shuffle_list = shuffleArray( shuffle_list ); // else add re-checked items to shufflelist
}
break;
default: shuffle_list = shuffleArray( updateTrackList() ); // init shufflelist when shuffle option selected
}
document.getElementById('content_audio').dataset.shufflelist = shuffle_list;
}
function getNextShuffledItem() { // ===> GET NEXT SHUFFLED ITEM
let shuffle_list = document.getElementById('content_audio').dataset.shufflelist.split(',');
let shuffled_item_id = shuffle_list.pop(); // get last item in shufflelist for playback
document.getElementById('content_audio').dataset.shufflelist = shuffle_list;
return shuffled_item_id;
}
function toggleChecked(e,id) { // ===> TOGGLE CHECKED single media checkboxes and update shufflelist
e.stopPropagation(); if ( hasClass('body','is_gecko') ) { e.preventDefault(); } // because Firefox sucks
document.getElementById(id).blur(); document.getElementById(id).classList.toggle('unchecked'); updateShuffleList(id);
}
function toggleAllChecked(e) { e.stopPropagation(); document.querySelectorAll('.directory_item.media input').forEach( el => el.click() ); updateShuffleList(); } // ===> TOGGLE ALL CHECKBOXES; update shufflelist
function isPlaying(id) { // ===> IS PLAYING; returns true if all conditions are true
switch(true) {
case !isTopWindow():
return ( hasClass('body','is_playing') ? true : false );
default: return ( id !== undefined && document.getElementById(id).currentTime > 0 && !document.getElementById(id).paused && !document.getElementById(id).ended );
}
}
function mediaSkip(e,args) { // ===> MEDIA SKIP +/-10/30 seconds
let factor, skip;
switch(true) {
case e !== undefined: factor = ( e.key === 'ArrowLeft' ? -1 : 1 ); skip = ( e.altKey && e.shiftKey ? 30 : e.altKey ? 10 : null ); break; // from top
case args !== undefined: factor = ( args[0] === 'ArrowLeft' ? -1 : 1 ); skip = args[1] || 0; break; // from iframe
}
const $player = ( getContentPaneData() === 'has_video' ? document.getElementById('content_video') : document.getElementById('audio') );
const time = $player.currentTime; // get current time
$player.currentTime = time + factor*(skip); // set new time
}
function playPauseMedia(task) { // ===> PLAY/PAUSE MEDIA
let $player = ( getContentPaneData() === 'has_video' ? document.getElementById('content_video') : document.getElementById('audio') );
switch(true) {
case $player === null: break;
case hasClass('#content_pane','has_iframe_audio'):
if (isPlaying( $player.id )) { $player.pause(); } else { $player.play(); } addClass('body','is_playing'); removeClass('body','is_paused'); sendMessage('iframe','setIframePlayerStatus','',task); break;
case isPlaying( $player.id ): try { $player.pause(); } catch(error) { null; } addClass('body','is_paused'); removeClass('body','is_playing'); break;
case task === 'play':
default: try { $player.play(); } catch(error) { return; } addClass('body','is_playing'); removeClass('body','is_paused');
}
}
function audioPlaybackOptions(id) { // ===> AUDIO PLAYBACK OPTIONS (shuffle, loop)
let $shuffle = document.getElementById('shuffle'), $loop = document.getElementById('loop');
switch(true) {
case id === 'shuffle': case id === 'shuffle_media_files': document.body.classList.toggle('shuffle_media'); updateShuffleList(); break;
case id === 'loop': case id === 'loop_media_files': document.body.classList.toggle('loop_media'); break;
}
switch(true) { // change audio checkboxes prop
case id === 'shuffle_media_files': ( $shuffle.checked === true ? $shuffle.checked = false : $shuffle.checked = true ); break;
case id === 'loop_media_files': ( $loop.checked === true ? $loop.checked = false : $loop.checked = true ); break;
}
}
function getMediaTimeRemaining() { // ===> GET MEDIA REMAINING TIME !!!Disabled: need to subtract time of already played
if ( !isTopWindow() || hasClass('#content_pane','has_iframe_audio') || document.getElementById('total_duration') === null ) { return; } // don't calculate time remaining for iframe media --> check later if this can/should be fixed
let total_time = document.getElementById('total_duration').dataset.total_duration, remaining_time = getFormattedDuration(total_time - document.getElementById('audio').currentTime);
document.querySelector('#total_duration').dataset.time_remaining = ' (Time Remaining: '+ remaining_time +')';
document.querySelector('#stats_details li.audio span.audio').dataset.audio_duration = ' (Time Remaining: '+ remaining_time +')';
}
//============================// PLAYLISTS/FILELISTS
function openPlaylist(file_name,reader,data) { // files & reader = open .m3u file; data = m3u.txtfile content// ===> OPEN PLAYLIST
if ( !hasClass('body','has_playlist') && !hasClass('body','has_filelist') ) { // if body does not already have playlist or filelist...
let body_classes = document.body.classList; // ...store original dir_list and body "has_"classes as data
document.getElementById('directory_list').dataset.dir_list = document.getElementById('directory_list').innerHTML; // store the original dir_list
document.getElementById('directory_list').dataset.data_classes = body_classes.value; // store the original body classes
body_classes.forEach( function(body_class) { if ( body_class.startsWith('has_') ) { document.body.classList.remove( body_class ); } }); // remove media and other body classes
}
removeClass('body','has_playlist has_filelist');
closeMenus();
file_name = ( file_name !== '' ? file_name : document.querySelector('.directory_item.selected.playlist .directory_item_name_a').innerText ); // get the file name for the title and current_dir_path
closeContent();
if ( !data.startsWith('#EXTM3U') ) { return; } // prevent reading non-playlist files
let new_index = buildNewIndex( '', convertPlaylist(data),'','playlist' ); // build the new dir_list
// body classes
if ( new_index[1].split(' ').filter(function(e) { return e !== 'has_media' && e !== 'has_audio' && e !== 'has_video' && e !==''; }).length > 0 || document.querySelectorAll('.directory_item.dir') !== undefined ) {
addClass('body','has_filelist'); // if playlist contains non-media files or dirs
} else {
addClass('body','has_playlist has_media'); // else normal media playlist
}
if ( /file:/.test(new_index) && !/file:/.test($protocol) ) { // show warning about local files on non-local page
addClass('body','has_warning');
addClass('#directory_list','local');
showWarning('warning_local_playlist');
}
// if ( !hasClass('#sort_by_name','','selected' ) ) { document.getElementById('sort_by_name').click(); } // sort by name
document.getElementById('directory_list').innerHTML = new_index[0]; // replace dir_list with prepared playlist
initEventListeners();
updateStats();
if ( hasClass('body','autoload_media') ) { autoLoadFile(); } // autoload media
scrollThis('#directory_list','.selected',false);
document.title = 'Playlist: '+ file_name;
document.querySelector('#current_dir_path span').innerHTML = file_name;
document.getElementById('open_playlist').value = ''; // clear form to allow new list to be loaded
}
function makePlaylist() { // ===> MAKE PLAYLIST
let items, playlist = [];
let playlist_type = document.querySelector('#make_playlist_form input:checked').id;
switch(playlist_type) { // get playlist items according to selected type
case 'media_files_only': items = document.querySelectorAll('.directory_item.media:not(.unchecked)'); break;
case 'audio_files_only': items = document.querySelectorAll('.directory_item.audio:not(.unchecked)'); break;
case 'video_files_only': items = document.querySelectorAll('.directory_item.video:not(.unchecked)'); break;
case 'all_non_media_files': items = document.querySelectorAll('.directory_item.not_media'); break;
case 'all_items': items = document.querySelectorAll('.directory_item'); break;
case 'directories_only': items = document.querySelectorAll('.directory_item.dir'); break;
case 'files_only': items = document.querySelectorAll('.directory_item.file'); break;
}
switch(true) {
case items.length === 0: showWarning('warning_no_playlist'); break; // show warning if no qualifying items found
default:
items.forEach(function(item) { playlist.push( makePlaylistEntry(item.id,true) ); }); // make playlist entry for each item
playlist = '#EXTM3U\n'+ playlist.join('\n'); // add playlist header id
saveFile( playlist,'audio/mpeg-url',(document.getElementById('current_dir_path').innerText).split('/').reverse()[1] +'.m3u' ); // save m3u with default name = current dir name
closeWarning(); // close warning
}
}
// Get playlist entry for display in title bar
function makePlaylistEntry(id,bool) { //*** id = 'content_title' or 'content_audio_title', bool = true --> from makePlaylist(), otherwise from showPlaylistEntry(); ***//
let title, link, duration, full_path = window.location.protocol + window.location.hostname + window.location.pathname;
title = ( getContentPaneData() === 'has_grid' && id === 'content_title' ? 'Files from: '+ full_path : bool === true ? document.getElementById(id).dataset.name : document.getElementById(id).innerText ); // get title txt
switch(true) { // Get link
case bool === true: link = document.getElementById(id).querySelector('a').href; break; // get item link for filelists
case getContentPaneData() === 'has_grid' && id === 'content_title': link = full_path; break; // grid link = dir path
case id === 'content_title': link = ( getContentPaneData() === 'has_image' ? getAttribute('#content_image','src') : getAttribute('.content.has_content','src') ); break; // content link
case id === 'content_audio_title': link = getLinkInfo( getAttribute('#audio','src') )[0].trim(); break; // audio link
}
link = ( link.startsWith('/') ? window.location.protocol +'//'+ link : link ); // fix links without protocols (local files)
link = new URL(link); // make new URL from link
link = link.protocol +'//'+ link.hostname + link.pathname; // compose link
duration = ( getContentPaneData() === 'has_video' ? Number.parseInt(document.getElementById('video').duration) : id === 'content_audio_title' ? Number.parseInt(document.getElementById('audio').duration) : '' );
return '#EXTINF:'+ duration +','+ title +'\n'+ link +'\n'; // return composed playlist entry
}
// Show Playlist Entry
function showPlaylistEntry(id) { // ===> SHOW PLAYLIST ENTRY
let el_id = ( id === 'content_title' ? 'content_playlist' : id === 'content_audio_title' ? 'content_audio_playlist' : '' );
document.getElementById(el_id).classList.toggle('has_content');
document.getElementById(el_id).querySelector('textarea').value = makePlaylistEntry(id);
selectTextareaContent(document.getElementById(el_id).querySelector('textarea').id); // add entry to the textarea
document.getElementById(el_id).querySelector('textarea').focus();
}
//============================// CUESHEETS
function getCueSheet(id,link,kind) { // id = 'content_iframe_file' or 'dir_list.media.id', link = selected.href, kind = audio/video // ===> GET CUE SHEET
if ( id === 'content_iframe_file' ) { return; } // prevent error for iframe files
removeClass('.cue_sheet_track_list_container','has_cue_sheet'); // reset cuesheet container
document.querySelector('#cue_sheet_track_list_audio').innerHTML = ''; // empty existing cue sheet track list
let media_file_name = document.getElementById(id).dataset.name;
let cue_file_name = media_file_name.slice(0,media_file_name.lastIndexOf('.')) + '.cuetxt';
let cue_file = document.querySelector('.directory_item.code[data-name="'+ cue_file_name +'"'); // get the cuesheet id
if ( cue_file !== null ) {
cue_file = document.getElementById( cue_file.id );
setAttribute('#content_iframe_utility','src',(cue_file.querySelector('a').href).trim() ); // load cuetxt in utility iframe for processing
switch(kind) { // get durations
case 'audio': setAttribute('#cue_sheet_track_list_container_audio','data-duration',document.getElementById('audio').duration); break;
case 'video': setAttribute('#cue_sheet_track_list_container_video','data-duration',document.getElementById('content_video').duration); break;
}
}
}
function processCueSheet(cue_sheet_text) { // ===> PROCESS CUE SHEET
let cue_sheet_tracks, track, command, prepped_track_list = [], track_id, display_time, index, index_sec;
cue_sheet_text = cue_sheet_text.replace(/\t/g,' ');
if ( !cue_sheet_text.startsWith('TRACK') ) { // let cue_sheet_info = cue_sheet_text.slice(0,cue_sheet_text.indexOf('TRACK ')); // if there is a file info header, remove it;
cue_sheet_tracks = cue_sheet_text.slice(cue_sheet_text.indexOf('TRACK ')).split('TRACK ').reverse();
} else {
cue_sheet_tracks = cue_sheet_text.split('TRACK ').reverse();
}
for ( track of cue_sheet_tracks ) { // for each track in the cue sheet...
let prepped_track = [];
track = track.trim().split(/[\n\r]/);
track_id = track.shift().split(' ')[0];
for ( command of track ) { // for each cue command (e.g.: PERFORMER "Artist")...
command = command.replace(/\"$/m,'').trim(); // ...trim and split into command and value
switch(true) {
//case ( /^PERFORMER\s+/.test(command) ): prepped_track[1] = '<span class="cue_performer">'+ command.replace(/^PERFORMER\s+\"/,'') +'</span>'; break; // artist name -- omit;already in titlebar
case ( /^TITLE\s+/.test(command) ): prepped_track[2] = '<span class="cue_title">'+ track_id +' '+ command.replace(/^TITLE\s*\"/,'').replace(/\"$/,'') +'</span>'; break; // track title
case ( /^INDEX\s+01\s+/.test(command) ): // time index
display_time = command.replace(/INDEX\s+\d+\s+/,'');
index = display_time.split(':').reverse(); // N.B.: cue time format is mm:ss:ff (ff = frames @ 75 frames/sec)
index_sec = index[0]/75 + index[1]*1 + index[2]*60; // convert index to seconds
prepped_track[3] = '<span class="cue_index align_right">'+ display_time +'</span></li>';
break;
}
}
prepped_track[0] = '<li id="track_'+ track_id +'" class="cue_sheet_track background_grey_85 pointer padding_4_6" data-duration="'+ index_sec +'">';
//if ( prepped_track[1] === undefined || ( /cue_performer"><\//.test(prepped_track[1]) ) ) { prepped_track[1] = '<span class="cue_performer">[missing]</span>'; }
if ( prepped_track[2] === undefined || ( /cue_title"><\//.test(prepped_track[2]) ) ) { prepped_track[2] = '<span class="cue_title">[missing]</span>'; }
if ( prepped_track.length > 3 ) { prepped_track_list.push(prepped_track.join('')); } // prepped_track.length > 3 to prevent adding empty track
}
prepped_track_list = prepped_track_list.reverse().join('');
switch(true) {
case document.querySelector('.directory_item.audio_loaded') !== null:
case hasClass('#content_pane','has_iframe_audio'):
addClass('#cue_sheet_track_list_container_audio','has_cue_sheet');
// TEMPPPPPPPPPPPP testing
//document.getElementById('cue_sheet_track_list_container_audio').insertAdjacentHTML('beforeend',prepped_track_list); // add cue sheet track list to menu
//document.getElementById('cue_sheet_track_list_audio').insertAdjacentHTML('afterbegin',`<li id="cue_sheet_title" class="cue_sheet_track display_flex header background_grey_85 border_top"><span>${ getLinkInfo( getAttribute('#content_iframe_utility','src') )[1] }</span></li>`); // add cue sheet track list title to menu
document.getElementById('cue_sheet_track_list_audio').insertAdjacentHTML('beforeend',prepped_track_list); // add cue sheet track list to menu
break;
case document.querySelector('.directory_item.video.content_loaded') !== null: // nobreak
case hasClass('#content_pane','has_iframe_file'):
addClass('#cue_sheet_track_list_container_video','has_cue_sheet');
document.getElementById('cue_sheet_track_list_container_video').insertAdjacentHTML('afterbegin',prepped_track_list); // add cue sheet track list to menu
break;
}
initCueSheetListEventListeners(); // init cuesheet event listeners
if ( document.querySelectorAll('.cue_sheet_track_list_container') !== null ) {
document.querySelectorAll('.cue_sheet_track_list_container').onmouseenter = function() { // add selected class to first track whose time is less than currentTime
if ( this.id === 'cue_sheet_track_list_container_video' ) { this.querySelector('.cue_sheet_track_list').style.top = this.clientHeight; }
let currentTime = document.getElementById('audio').currentTime;
setAttribute( this.querySelector('.cue_sheet_track_list'),'data-duration',currentTime ); // set duration attr of cue sheet track list
this.querySelector('.cue_sheet_track_list').style.height = document.getElementById('content_container').height + document.getElementById('content_title_container').height; // set height of cue sheet track list
let currentTrack = this.querySelector('.cue_sheet_track_list li').filter(function() { return parseInt( this.dataset.duration ) <= Math.round(currentTime); }).last();
currentTrack.parentElement.children.classList.remove('selected');
currentTrack.classList.add('selected');
};
}
}
//============================// IMAGES
function getImageDimensions(link, callback) {
if ( link !== undefined ) { let img = new Image(); img.src = link; img.onload = function() { callback( this.width, this.height ); }; img = null; }
}
function setImageDimensions() { // ===> SET IMAGE DIMENSIONS
switch(true) {
case !isTopWindow(): break; // ignore if iframe
case getContentPaneData() === 'has_image':
getImageDimensions( getAttribute('#content_image','src'), function( width,height ) { // getImageDimensions()
let percentage = (( document.getElementById('content_image').width/width ) * 100 ).toFixed(1); // define percentage
setAttribute('#content_title span','data-after',' ('+ width +'px × '+ height +'px) ('+ percentage +'%)' ); // set dataset.after for content_title
}); break;
default: removeAttribute('#content_title span',['data-after']); // remove image dimensions
}
}
function showImageThumbnails() {
let image_files = document.querySelectorAll('.directory_item.image'), image_url, current_background_image; // Add/remove image thumbnails as background icons
if ( image_files !== null ) {
Array.from(image_files).forEach( function(image) {
current_background_image = image.querySelector('a .has_icon_before_before').style.backgroundImage;
switch(true) { // toggle thumbnail display
case !hasClass('body','show_image_thumbnails'): // show default icon, don't remove existing thumbnail
image.querySelector('a .has_icon_before_before').style.backgroundImage = get_SVG_UI_File_Icon('file_icon_image') +','+ current_background_image; // only first background image is visible
break;
default: // remove default image icon or load image thumbnail
image_url = ( current_background_image.indexOf('\\),\surl') ? current_background_image.replace(/^.+?\),\surl/m,'url') : 'url("'+ image.querySelector('a').href +'")' );
image.querySelector('a .has_icon_before_before').style.backgroundImage = 'url("'+ image.querySelector('a').href +'")';
}
});
}
}
//============================// FONTS
function setContentFontSource(id,bool,font_grid,link,i,font_files_length) { // ===> SHOW FONT (and create font items) row
// bool = true if for show font grid, id from fontGridItems(); link = from previewed directory
let font_styles = document.getElementById('font_styles'), border_class = '', last_item_class = '';
let font_family = ( link !== '' ? link.slice(link.lastIndexOf('/') + 1,link.lastIndexOf('.')) : document.getElementById(id).dataset.name ); font_family = decodeURIComponentSafe(font_family);
let font_url = ( link !== '' ? link : getAttribute('#'+ id +' a','href') ); font_url = decodeURIComponentSafe(font_url);
switch(true) {
case bool === false: // just show selected font
if ( font_styles.innerHTML !== '' ) { font_styles.innerHTML = ''; } // delete previous @font-face rule
font_styles.insertAdjacentHTML( 'beforeend',`@font-face { font-family: "${ font_family }"; src: url("${ font_url }"); }` );
if ( document.getElementById('content_font') !== null ) { document.getElementById('font_specimen').style.fontFamily = '"'+font_family+'"'; } // set content font style
break;
case font_grid === 'font_grid': { // else make font grid items
const display_name = font_family;
if ( i === font_files_length - 1 ) { last_item_class = 'border_bottom_x'; }
if ( i > 0 ) { border_class = 'border_top_x'; }
let font_grid_item_info = `<p class="margin_0 text_color_default">${ display_name.toUpperCase() }</p><h2 style=\'font-family: "${ font_family }"\'; class="margin_0"><a class="text_color_default" href="${ font_url }">${ display_name.slice(0,font_family.lastIndexOf(".")) }</a></h2>`;
let $font_grid_item_el = `<div class="font_grid_item ${border_class} ${last_item_class} background_grey_95" data-id="${ id }" data-kind="font">${ font_grid_item_info }</div>`;
document.getElementById('font_grid_styles').insertAdjacentHTML('beforeend', `@font-face { font-family: "${ font_family }"; src: url("${ font_url }"); }`);
return $font_grid_item_el;
}
}
}
function hexToDecimal(d) { return parseInt(d, 16); }
function decimalToHex(d, padding) { var hex = Number(d).toString(16); hex = "000000".substr(0, padding - hex.length) + hex; return hex; }
function unicodeChars(char_block,bool) {
let chars = '';
let Char_Ranges = {
BMP_Range_01: {start:'0000',end:'0FFF'}, BMP_Range_02: {start:'1000',end:'1FFF'}, BMP_Range_03: {start:'2000',end:'2FFF'}, BMP_Range_04: {start:'3000',end:'3FFF'}, BMP_Range_05: {start:'4000',end:'4FFF'}, BMP_Range_06: {start:'5000',end:'5FFF'}, BMP_Range_07: {start:'6000',end:'6FFF'}, BMP_Range_08: {start:'7000',end:'7FFF'}, BMP_Range_09: {start:'8000',end:'8FFF'}, BMP_Range_10: {start:'9000',end:'9FFF'}, BMP_Range_11: {start:'A000',end:'AFFF'}, BMP_Range_12: {start:'B000',end:'BFFF'}, BMP_Range_13: {start:'C000',end:'CFFF'}, BMP_Range_14: {start:'D000',end:'DFFF'}, BMP_Range_15: {start:'E000',end:'EFFF'}, BMP_Range_16: {start:'F000',end:'FFFF'}, SMP_Range_01: {start:'10000',end:'10FFF'}, SMP_Range_02: {start:'11000',end:'11FFF'}, SMP_Range_03: {start:'12000',end:'12FFF'}, SMP_Range_04: {start:'13000',end:'13FFF'}, SMP_Range_05: {start:'14000',end:'14FFF'}, SMP_Range_06: {start:'15000',end:'15FFF'}, SMP_Range_07: {start:'16000',end:'16FFF'}, SMP_Range_08: {start:'17000',end:'17FFF'}, SMP_Range_09: {start:'18000',end:'18FFF'}, SMP_Range_10: {start:'19000',end:'19FFF'}, SMP_Range_11: {start:'1A000',end:'1AFFF'}, SMP_Range_12: {start:'1B000',end:'1BFFF'}, SMP_Range_13: {start:'1C000',end:'1CFFF'}, SMP_Range_14: {start:'1D000',end:'1DFFF'}, SMP_Range_15: {start:'1E000',end:'1EFFF'}, SMP_Range_16: {start:'1F000',end:'1FFFF'}, SIP_Range_01: {start:'20000',end:'20FFF'}, SIP_Range_02: {start:'21000',end:'21FFF'}, SIP_Range_03: {start:'22000',end:'22FFF'}, SIP_Range_04: {start:'23000',end:'23FFF'}, SIP_Range_05: {start:'24000',end:'24FFF'}, SIP_Range_06: {start:'25000',end:'25FFF'}, SIP_Range_07: {start:'26000',end:'26FFF'}, SIP_Range_08: {start:'27000',end:'27FFF'}, SIP_Range_09: {start:'28000',end:'28FFF'}, SIP_Range_10: {start:'29000',end:'29FFF'}, SIP_Range_11: {start:'2A000',end:'2AFFF'}, SIP_Range_12: {start:'2B000',end:'2BFFF'}, SIP_Range_13: {start:'2C000',end:'2CFFF'}, SIP_Range_14: {start:'2D000',end:'2DFFF'}, SIP_Range_15: {start:'2E000',end:'2EFFF'}, SIP_Range_16: {start:'2F000',end:'2FFFF'}, TIP_Range_01: {start:'30000',end:'30FFF'}, TIP_Range_02: {start:'31000',end:'31FFF'}, SSP_Range_01: {start:'E0000',end:'E0FFF'}
}
// let ligs = ['42802','42803','198','230','42804','42805','42806','42807','42808','42809','42810','42811','42812','42813','42830','42831','42848','42849','64256','64257','64258','64259','64260','64261','64262','64263','64264','64265','64266','64267','64268','64269','64272','64273','64274','405','8468','7930','7931','306','307','338','339','43874','7838','223','42792','42793','7531','43875'];
switch(char_block) {
case '': document.getElementById('font_specimen_grid').innerHTML = ''; break;
// case 'ligs': ligs.forEach(lig => chars += '<div id="glyph_specimen_'+lig+'" data-char="'+lig+'" data-unicode_hex="U+'+decimalToHex(lig,4)+'" class="border_right_x border_bottom_x background_grey_95">&#'+lig+';</div>' ); break;
default:
for ( let i = hexToDecimal(Char_Ranges[char_block].start); i < hexToDecimal(Char_Ranges[char_block].end) + 1; i++ ) {
chars += '<div id="glyph_specimen_'+i+'" data-char="'+i+'" data-unicode_hex="U+'+decimalToHex(i,4)+'" class="border_right_x border_bottom_x background_grey_95">&#'+i+';</div>';
}
}
if (bool) { return chars; } else if ( chars !== '' ) {
closeGlyph('specimen');
document.getElementById('font_specimen_grid').innerHTML = chars.trim(); focusContent();
document.querySelectorAll('#font_specimen_grid div').forEach( el => el.onclick = function(e) { e.stopPropagation(); showFontGlyph(el.id); });
document.querySelector('#specimen_glyph').onmousedown = function(e) { e.stopPropagation();
let startX = e.pageX, startY = e.pageY, elOffsetLeft = document.querySelector('#specimen_glyph').offsetLeft, elOffsetTop = document.querySelector('#specimen_glyph').offsetTop;
document.onmousemove = function(e) { mouseMove(e,'#specimen_glyph',startX,startY,elOffsetLeft,elOffsetTop) }
};
}
}
function resetFontFeatureSettings(id) { // REMOVE FONT FEATURES AND VARIANTS ( if id === undefined > remove all )
let el_ids = ['font_variant_select','font_tag_textarea','font_stylistic_set_select'], el;
document.getElementById('font_specimen').style.removeProperty('font-feature-settings');
el_ids.forEach(function(el_id) { if ( el_id === id ) { return } else { el = document.getElementById(el_id); if ( el.tagName.toLowerCase() === 'select' ) { el.selectedIndex = 0 } else { el.value = '' } } });
}
function setFontFeatureSettings(value,id) {
let font_specimen = document.getElementById('font_specimen');
if ( id === 'font_tag_textarea' && value.length !== 4 ) { return; } // else { document.activeElement.blur(); }
resetFontFeatureSettings(id);
switch(true) {
case (/cv01/.test(value)) && id !== 'font_tag_textarea': document.getElementById('font_tag_textarea').value = value; document.getElementById('font_tag_textarea').focus(); break;
case (/ss01/.test(value)) && id !== 'font_tag_textarea': document.getElementById('font_stylistic_set_select').value = value; document.getElementById('font_stylistic_set_select').focus(); break;
default: font_specimen.style.setProperty('font-feature-settings','\"'+value+'\"');
}
}
function openFontFile(files,reader) { // ===> OPEN FONT FILE
closeContent(); hideGrid(); // close content & hide grids
setAttribute('#content_pane','data-content','has_font_file');
addClass('#content_font','has_content');
document.querySelector('#glyph_viewer').dataset.scale = 1;
parseFont(reader.result); // parseFont()
document.querySelector('#content_title span').innerHTML = files.name;
document.getElementById('open_font').value = ''; // reset input to allow same font to be reopened immediately after closing.
document.querySelectorAll('.glyph_container').forEach( el => el.addEventListener('click', () => showFontGlyph(el.id))) ; // init click listener for each glyph
focusContent(); // focus content
}
function parseFont(fontblob) { // ===> PARSE FONT (req opentype.js)
let font = window.opentype.parse(fontblob);
let font_file_grid = document.getElementById('font_file_grid');
let glyph_container, glyph_canvas_el, glyph_info, glyph, glyph_width, glyph_height, context_X, bounding_box, glyph_unicode, this_glyph, context;
let glyph_path, glyph_SVG, viewer_height = document.getElementById('content_font').offsetHeight;
document.getElementById('content_font').font_glyphs = font.glyphs; // store glyphs data in $content_font as property font_glyphs
document.getElementById('glyph_viewer').dataset.font_name = font.names.fullName.en; // add font name to glyph viewer dataset
// Draw glyphs
let font_glyphs = font.glyphs, glyphs = '';
for ( let i = 0; i < font_glyphs.length; i++ ) {
glyph = font_glyphs.glyphs[i]; // get glyph
glyph_unicode = ( glyph.unicode !== undefined ? '#'+ glyph.unicode : glyph.unicode ); // glyph unicode info
glyph_info = `<div class="glyph_info text_color_333 display_flex"><span>${glyph.index}</span><span>${glyph.name}</span><span>${glyph_unicode}</span></div>`; // glyph info
glyph_path = glyph.getPath(0,100,72);
glyph_SVG = '<svg xmlns=\'http://www.w3.org/2000/svg\' class="invert" x=\'0px\' y=\'0px\' viewBox=\'-40 20 140 140\' xml:space=\'preserve\' preserveAspectRatio=\'xMidYMid meet\'><g>'+ glyph_path.toSVG().replace(/"/g,'\'') +'</g></svg>';
glyph_container = `<div id="glyph_container_${glyph.index}" class="glyph_container flex_justify_center background_grey_95 border_right_x border_bottom_x position_relative" data-id="glyph_container_${glyph.index}">${glyph_SVG}${glyph_info}</div>`; // glyph container
font_file_grid.insertAdjacentHTML('beforeend',glyph_container); // insert glyph into grid
}
font_file_grid.insertAdjacentHTML('beforeend',glyphs); // insert glyph into grid
getFontInfo(font); // get font info
}
function getFontInfo(font) { // ===> GET FONT INFO
let font_names = font.names, font_info_details = '';
for ( let name in font_names ) {
let value = font_names[name].en;
if ( name.endsWith('URL') ) {
let href = ( !value.startsWith('http') ? 'http://'+ value : value); // in case url without protocol is used
value = '<a class="bold" href="'+ href +'" target="_blank">'+ value +'</a>';
}
font_info_details += `<li class="display_none"><span class="col_1 font_info_name align_right"> ${name}: </span><span class="font_info_value">${value}</span></li>`;
}
let num_glyphs = font.numGlyphs; // number of glyphs in font
let $font_info = `<ul id="font_info" class="info_list text_color_default background_grey_80 position_fixed display_none">
<li class="info_list_header center bold"><span>FONT INFO:${font.names.fullName.en.toUpperCase()}</span></li>
${font_info_details}
<li class="display_none"><span class="col_1 font_info_name align_right">numGlyphs: </span><span class="font_info_value">${num_glyphs}</span></li>
</ul>`;
document.getElementById('font_file_viewer').insertAdjacentHTML('beforeend',$font_info);
}
function showFontGlyph(id) { // ===> SHOW INDIVIDUAL GLYPH
switch(true) {
case getContentPaneData() === 'has_font': {
let this_glyph = document.getElementById(id), toolbar = document.getElementById('font_toolbar'), specimen_glyph = document.getElementById('specimen_glyph');
removeClass('#font_specimen_grid .selected','selected'); document.getElementById(id).classList.add('selected'); // remove/add selected class to glyphs
specimen_glyph.innerText = String.fromCodePoint(this_glyph.dataset.char); // add glyph to specimen glyph
toolbar.dataset.char = 'Unicode Dec: '+this_glyph.dataset.char; toolbar.dataset.unicode_hex = 'Unicode Hex: '+ this_glyph.dataset.unicode_hex; // add data to toolbar for glyph info display
break;
}
case ( /has_font_file|has_glyph/.test(getContentPaneData()) ): {
addRemoveClassSiblings('#'+ id,'selected');
let $glyph_viewer = document.getElementById('glyph_viewer');
let glyph_width = document.getElementById(id).dataset.glyph_width, glyph_height = document.getElementById(id).dataset.glyph_height;
let viewer_width, viewer_height = document.getElementById('content_font').offsetHeight;
let glyphID = id.slice(id.lastIndexOf('_') + 1); // convert id to number only
let glyphs_data = document.getElementById('content_font').font_glyphs; // get glyphs from stored data
let glyph = glyphs_data.glyphs[glyphID]; // get glyph by id
let glyph_path = glyph.getPath(0,viewer_height/14,72); // get glyph path
let data_glyph_SVG = glyph_path.toSVG().replace(/"/g,'\''); // get glyph SVG data
let glyph_scale = document.querySelector('#glyph_viewer').dataset.scale; // get current glyph scale factor
glyph_scale = 'style="transform:scale('+ glyph_scale +')"'; // format glyph scale style
if ( document.querySelector('#glyph_container') !== null ) { document.querySelector('#glyph_container').remove(); } // remove any existing svg element
let SVG_prefix = `<div id="glyph_container" class="flex_justify_center"><div id="svg_container" class="flex_justify_center position_relative"><svg xmlns=\'http://www.w3.org/2000/svg\' x=\'0px\' y=\'0px\' viewBox=\'0 20 40 40\' xml:space=\'preserve\' preserveAspectRatio=\'xMidYMid meet\'><g ${glyph_scale}>`;
$glyph_viewer.insertAdjacentHTML('beforeend',SVG_prefix + data_glyph_SVG +'</g></svg></div></div>'); // add the svg glyph to glyph_viewer
$glyph_viewer.dataset.raw_svg = data_glyph_SVG; // store the raw svg data for saving svg to file
$glyph_viewer.dataset.glyph_name = glyph.name; // store the glyph name for saving svg to file
document.querySelector('#glyph_viewer_info div').innerText = glyphID +': '+ glyph.name +', #'+ glyph.unicode; // add glyph info to glyph viewer title bar
setAttribute('#content_pane','data-content','has_glyph');
document.querySelector('#svg_container').onmousedown = function(e) { e.stopPropagation();
let startX = e.pageX, startY = e.pageY, elOffsetLeft = document.querySelector('#svg_container').offsetLeft, elOffsetTop = document.querySelector('#svg_container').offsetTop;
document.onmousemove = function(e) { mouseMove(e,'#svg_container',startX,startY,elOffsetLeft,elOffsetTop) }
};
break;
}
}
}
function saveGlyph() { // ===> SAVE GLYPH SVG
let $glyph_viewer = document.getElementById('glyph_viewer');
let file_name = $glyph_viewer.dataset.font_name +'—'+ $glyph_viewer.dataset.glyph_name +'.svg';
let data_prefix = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 120 120" xml:space="preserve" preserveAspectRatio=\'xMidYMin meet\'><g>';
let data_suffix = '</g></svg>';
let data = data_prefix + document.getElementById('glyph_viewer').dataset.raw_svg + data_suffix;
saveFile(data,'image/svg+xml',file_name);
}
//============================// GRIDS
function showGrid(id) { // ===> SHOW GRID
switch(true) {
case !isTopWindow(): // IFRAME GRIDS
switch(true) {
case hasClass('body','has_grid'): removeClass('body','has_grid'); break;
default: addClass('body','has_grid'); showImageThumbnails();
}
break;
default:
if ( id !== undefined ) { makeGrids(id); } // initial make grid items; otherwise, just unhide existing grid (see below)
closeContent();
let selected_ID;
if ( document.querySelector('.directory_item.selected') !== null ) { selected_ID = document.querySelector('.directory_item.selected').id; }
addClass('#show_grid','has_grid');
setAttribute('#content_pane','data-content','has_grid');
removeClass('#content_pane','has_hidden_grid');
removeClass('#content_pane div','selected hovered');
addClass('#content_pane div[data-id="'+ selected_ID +'"]','selected');
focusContent();
}
}
function hideGrid() { if ( getContentPaneData() === 'has_grid' ) { removeAttribute('#content_pane',['data-content']); addClass('#content_pane','has_hidden_grid'); } } // ===> HIDE GRID
function closeGrid() { // ===> CLOSE GRID (remove grid elements, etc.)
removeClass('#show_grid','has_grid');
if ( getContentPaneData() === 'has_grid' ) { removeAttribute('#content_pane',['data-content']); }
removeClass('#content_pane','has_image_grid has_font_grid');
removeAttribute('#content_grid',['style']);
document.getElementById('content_grid').innerHTML = '';
if ( document.querySelector('.directory_item.selected') !== null ) { showThis(document.querySelector('.directory_item.selected').id); }
}
function showGridItem(e,id,src,kind) { e.preventDefault(); // ===> SHOW GRID ITEM
hideGrid();
switch(true) {
case document.getElementById(id) !== null: addRemoveClassSiblings('#'+ id,'selected','selected'); document.getElementById(id).click(); break; // normal grid item display
case document.getElementById(id) === null: showContent('', [src,kind]); break; // show grid items from closed subdirectory
}
}
// ***** IMAGE/FONT GRID SETUP
function fontGridItems() { // ===> FONT GRID ITEMS
let $font_grid_items = '', $font_files = document.querySelectorAll('.directory_item.font'), new_grid_item, font_files_length = $font_files.length;
document.getElementById('font_grid_styles').innerHTML = '';
for ( let i = 0; i < font_files_length; i++ ) { // for each font...
new_grid_item = setContentFontSource( $font_files[i].id, true, 'font_grid','',i,font_files_length ); // make new font_grid_item
$font_grid_items += new_grid_item; // add it to the font_grid_items
}
return $font_grid_items; // return font_grid_items
}
function imageGridItems() { // ===> IMAGE GRID ITEMS
let $image_grid_items = '', $image_files = document.querySelectorAll('.directory_item.image:not(.ignored)'), classes = 'image_grid_item flex_justify_center border_right_x border_bottom_x', image_files_length = $image_files.length;
for ( let i = 0; i < image_files_length; i++ ) {
const id = $image_files[i].id;
const this_link = $image_files[i].querySelector('a').href;
const exts = $item_kind.image.filter( ext => !$row_settings.ignored.includes(ext) ); // decide which image files can be displayed
const title_name = this_link.slice(this_link.lastIndexOf('/') + 1);
if ( exts.includes($image_files[i].dataset.ext) ) { // if item ext is in the image extension array...
let item = `<div class="${ classes } background_grey_90" data-ID="${ id }" data-index="${ i }" data-kind="image"><a href="${ this_link }"><img src="${ this_link }" title="${ title_name }" loading="lazy" /></a></div>`; // make new image_grid_item
$image_grid_items += item; // ...add it to the image_grid_items
}
}
return $image_grid_items; // return image_grid_items
}
function makeGrids(id) { // ===> MAKE GRIDS
document.querySelector('#content_grid').innerHTML = ''; // remove previous grid items
removeClass('#content_pane','has_hidden_grid has_image_grid has_font_grid'); // reset content_pane grid classes
switch(true) { // determine which grid type to make
case id === 'show_font_grid': addClass('#content_pane','has_font_grid'); document.getElementById('content_grid').innerHTML = fontGridItems(); break; // only show font grid
case id === 'show_image_grid': addClass('#content_pane','has_image_grid'); document.getElementById('content_grid').innerHTML = imageGridItems(); break; // only show image grid
default: document.getElementById('content_grid').innerHTML = imageGridItems() + fontGridItems(); // show grid of both images and fonts
}
initGridItemEventListeners(); // register event watchers for added grid elements
}
// ***** IMAGE/FONT/GLYPH SCALE
const getFontSize = function(el) { return parseFloat(window.getComputedStyle(el).fontSize); };
function scaleFonts(e, incr, id) { // ===> SCALE FONTS
let $content_grid = document.getElementById('content_grid'), $content_font = document.getElementById('content_font');
const font_size = parseInt(getComputedStyle(document.body).fontSize); // pts/em
if ( id === 'decrease' ) { incr = 1/incr; }
if ( getContentPaneData() === 'has_grid' ) { $content_grid.style.fontSize = ( getFontSize($content_grid)/font_size * incr ) +'em'; return; }
if ( getContentPaneData() === 'has_font' ) { $content_font.style.fontSize = ( getFontSize($content_font)/font_size * incr ) +'em'; return; }
scrollThis('#content_container','#content_font');
}
function scaleGlyphs(e, incr, id) { // ===> SCALE GLYPHS
if ( id === 'decrease' ) { incr = 1/incr; }
let $glyph_viewer, current_scale;
switch(true) {
case document.getElementById('specimen_glyph').innerText !== '':
document.getElementById('specimen_glyph').style.fontSize = ( getFontSize(document.getElementById('specimen_glyph')) * incr) +'px'; break;
default:
$glyph_viewer = document.getElementById('glyph_viewer'), current_scale = ( $glyph_viewer.dataset.scale === undefined ? incr : incr * $glyph_viewer.dataset.scale );
$glyph_viewer.querySelector('svg g').style.transform = 'scale('+ current_scale * 100 +'%)';
$glyph_viewer.dataset.scale = current_scale.toFixed(2);
}
}
function scaleGridImages(incr,id) { // ===> SCALE IMAGE GRID ITEMS
if ( id === 'decrease' ) { incr = 1/incr; }
const $image_grid_item = document.querySelectorAll('.image_grid_item img');
let image_grid_item_width = Number.parseFloat( $image_grid_item[0].offsetWidth,10) * incr;
let image_grid_item_height = Number.parseFloat( $image_grid_item[0].offsetHeight,10) * incr;
let image_grid_item_max_width = Number.parseFloat( $image_grid_item[0].style.maxWidth,10) * incr;
let image_grid_item_max_height = Number.parseFloat( $image_grid_item[0].style.maxHeight,10) * incr;
// prevent reducing grid image size on first scale click:
if ( image_grid_item_width < image_grid_item_max_width ) { image_grid_item_width = image_grid_item_max_width; }
if ( image_grid_item_height < image_grid_item_max_height ) { image_grid_item_height = image_grid_item_max_height; }
// set grid properties
document.getElementById('content_grid').style.gridTemplateColumns = 'repeat(auto-fill, minmax('+ (image_grid_item_width + 16) +'px, auto ) )';
Array.from($image_grid_item).forEach( function(el) { el.style.maxWidth = image_grid_item_width +'px'; el.style.maxHeight = image_grid_item_height +'px'; });
return;
}
function scaleImages(e,incr,id) { // ===> ZOOM IMAGES ON CLICK
const $content_container = ( document.getElementById('iframe_body') !== null && document.guerySelector('#iframe_body > img') !== null ? document.getElementById('iframe_body') : document.getElementById('content_container') );
let $this_image = ( document.querySelector('#iframe_body > img') !== null ? document.querySelector('#iframe_body > img') : document.getElementById('content_image') ); // define $this_image
let CC_width = function() { return Math.round($content_container.offsetWidth); }, CC_height = function() { return Math.round($content_container.offsetHeight); }; // content_container dimensions
let this_width = Math.round($this_image.offsetWidth), this_height = Math.round($this_image.offsetHeight); // $this_image dimensions
const iframe_delta = ( document.querySelectorAll('#iframe_body > img').length === 1 ? Number.parseInt(document.getElementById('iframe_body').style.padding) : 0 ); //
switch(true) {
case getContentPaneData() === 'has_grid': scaleGridImages(incr,id); break; // scale grid images
default: // scale single images
getImageDimensions( $this_image.src, function( width, height ) {
switch(true) {
case incr !== undefined && id !== undefined: // scale images by increment
addClass('#content_pane','has_scaled_image'); removeClass('#content_pane','has_zoom_image'); // remove zoom classes in case window resized after zoom
switch(true) {
case id === 'increase': $this_image.style.cssText = `width:${this_width * incr}px; height:auto; max-width:none; max-height:none;`; break;
case id === 'decrease' && ( this_width >= 1 && this_height >= 1 ): $this_image.style.cssText = `width:${this_width / incr}px; height:auto; max-width:none; max-height:none;`; break;
}
// keep images centered when scaling
if ( Math.round($this_image.offsetWidth) >= CC_width() ) { document.getElementById('content_image_container').scrollLeft = ( Math.round( $this_image.offsetWidth ) - CC_width() )/2; }
if ( Math.round($this_image.offsetHeight) <= CC_height() ) {
document.getElementById('content_image_container').scrollTop = ( CC_height() - Math.round( $this_image.offsetHeight ) )/2;
} else {
document.getElementById('content_image_container').scrollTop = ( Math.round($this_image.offsetHeight) - CC_height() )/2;
}
break;
default: { // else zoom single image on click
$this_image.removeAttribute('style');
if ( width <= CC_width() && height <= CC_height() ) { removeClass('#content_pane','has_zoom_image has_scaled_image'); return; } // don't zoom small images, else zoom image:
const CC_offset = $content_container.getBoundingClientRect();
const img_offset = $this_image.getBoundingClientRect();
const percentX = (e.pageX - img_offset.left)/this_width; const percentY = (e.pageY - img_offset.top)/this_height; // x,y coordinates of zoom click as percentage of image width/height
const scrollX = (width * percentX) - e.pageX + CC_offset.left - (iframe_delta * width / this_width); // calculate clicked x-coordinates for full-size image
const scrollY = (height * percentY) - e.pageY + CC_offset.top - (iframe_delta * height / this_height); // calculate clicked y-coordinate for full-size image
removeClass('#content_pane','has_scaled_image'); // in case image scaled already
document.getElementById('content_pane').classList.toggle('has_zoom_image');
document.getElementById('content_image_container').scrollTo(scrollX,scrollY); // scroll to clicked position
}
}
});
setImageDimensions(); // set image dimensions
}
}
function scaleButtons(e,id) { // ===> SCALE BUTTONS
e.preventDefault(); e.stopPropagation();
switch(true) {
case document.getElementById('specimen_glyph').innerText !== '':
case getContentPaneData() === 'has_glyph': scaleGlyphs(e, 1.125, id); break;
default: scaleImages(e, 1.125, id); scaleFonts(e, 1.125, id); // scale glyphs or images and fonts
}
addClass('#reload_btn','reset'); // add reset class to reload button
if ( hasClass('body','focus_content') ) { focusContent(); } // focus content
}
//============================// SHOW TEXT EDITOR
function showTextEditor() { // ===> SHOW TEXT EDITOR
switch(true) {
case getContentPaneData() === 'has_text_editor': hideTextEditor(true); break; // hide open text editor
case !hasClass('body','has_text_editor_UI'): setUpTextEditorUI(); // no break; add the text editor UI if loading text editor for first time
default: // show editor
document.getElementById('content_video').pause;
removeClass('#content_pane','has_hidden_text_editor');
if ( getContentPaneData() === 'has_font_file' ) { addClass('#content_pane','has_hidden_font_file'); }
setAttribute('#content_pane','data-content','has_text_editor'); // empty title
addClass('body','has_text_editor');
setContentTitle();
focusContent();
}
}
function hideTextEditor(bool) { // ===> HIDE TEXT EDITOR
if ( getContentPaneData() === 'has_text_editor' ) {
removeClass('body','has_text_editor');
removeAttribute('#content_pane',['data-content']);
switch(true) {
case hasClass('#content_pane','has_hidden_font_file' ): removeClass('#content_pane','has_hidden_font_file'); setAttribute('#content_pane','data-content','has_font_file'); break;
case bool === true: addClass('#content_pane','has_hidden_text_editor'); break;
case document.querySelector('.directory_item.not_audio.selected') !== null:
showThis( document.querySelector('.directory_item.selected').id );
default: document.querySelector('#content_pane').dataset.content = 'has_null';
}
focusSidebar();
}
}
//============================// PDF
function setupContentPDF() { // ===> SET UP CONTENT_PDF
let $embed = '<embed id="content_pdf" class="content" tabindex="0" data-kind="pdf">'; // define new content_pdf
document.getElementById('content_pdf').remove(); // remove existing content_pdf el
document.getElementById('content_image_container').insertAdjacentHTML('afterend',$embed); // add new content_pdf el
}
//============================// LINK FILES
function openLinkFile(e) { // ===> OPEN LINK FILES on dblclick (webloc, url)
e.preventDefault(); e.stopPropagation();
let link = document.querySelector('.directory_item.selected').dataset.link_file_link; const regex = /URL\=(.+?)$|<key>URL<\/key>\s*<string\>(.+?)<\/string\>/im;
// regex for finding link
let url = ( link.match(regex)[1] || link.match(regex)[2] );
switch(true) {
case url === undefined: break;
case $protocol === 'file:' && url.startsWith('file'): // if local link files with local link location...
switch(true) { // open link files clicked in iframes in iframe
case !isTopWindow(): window.location = url; break;
default: showContent('open_link_file',[url,'link']); // open local link files to local items in content_iframe
}
break;
default: window.open(url); // else open link file links in new window
}
}
//============================// IFRAME CONTENT
function showIframeContent(args) { // ===> SHOW IFRAME CONTENT called when iframe loads
let link = ( /inetloc|url|webloc|view_directory_source/.test(args[0]) ? getAttribute('#content_iframe','src') : getAttribute('#content_iframe','data-content_name') );
let kind = getAttribute('#content_iframe','data-kind');
switch(true) { // set content_pane.dataset.content
case ( /app|dir/.test(kind) ): setAttribute('#content_pane','data-content','has_dir'); break; //
default: setAttribute('#content_pane','data-content','has_'+ kind);
}
if ( document.querySelector('.directory_item.content_loaded.htm') !== null ) { document.querySelector('.directory_item.htm.selected').dataset.html_content = args[1]; }
if ( !/subdirectory=true/.test(args[0]) ) { setAttribute('#content_pane','data-loaded','loaded'); setContentTitle(link,kind); } // show iframe, hide loading spinner, set title
if ( hasClass('body','focus_content') ) { focusContent(); } // focus content
}
//============================// DIRECTORY SOURCE
function showDirectorySource() { // ===> SHOW DIRECTORY SOURCE
switch(true) {
case hasClass('body','has_directory_source'): document.querySelector('#close_btn').click(); break; // close if open
default: addClass('body','has_directory_source'); showContent('view_directory_source',[current_location]); // show directory source
}
}
function openSidebarInContentPane() { showContent('open_sidebar_in_content_pane',[current_location,'dir']); focusContent(); } // ===> OPEN SIDEBAR IN CONTENT PANE
function openInTextEditor() { // ===> OPEN IN TEXT EDITOR
let scroll_script = '<script id="scroll_script">window.onscroll = function(){ window.parent.postMessage( { "messageContent":"scroll_iframe","functionName":"","arguments":window.scrollY },"*"); }</script><style>body{margin:0;padding:0;}</style>';
let html = document.querySelector('.directory_item.htm.content_loaded').dataset.html_content;
showTextEditor();
// showWarning(); // if texteditor edited, show warning
if ( html !== undefined ) { // set previewed text
document.getElementById('text_editor_raw_pane').value = html;
setAttribute('#text_editor_preview_pane','srcdoc',scroll_script + html);
document.getElementById('text_editor_html_pane').value = html;
}
}
// END SHOW INDIVIDUAL CONTENT TYPES
//============================//
// ***** MAIN SHOW CONTENT FUNCTIONS ***** //
function setContentClasses(id, kind, content_el) { // ===> SET CONTENT PANE CLASSES
switch(true) { // add content loaded classes
case id === '': break;
case kind === 'audio': removeClass('.audio:not(#'+ id +')','audio_loaded selected'); addClass('#'+ id,'selected'); break; // audio classes
default: addRemoveClassSiblings('#'+ id,'content_loaded'); // non-audio items
}
switch(true) { // set content_pane classes
case kind === 'audio': // audio
switch(true) {
case id === 'content_iframe_file': addClass('#content_pane','has_iframe_audio'); break; // iframe_audio
default: addClass('#content_pane','has_audio'); // sidebar audio
}
break;
case ( /app|dir/.test(kind) ): setAttribute('#content_pane','data-content','has_dir'); // apps and directories
switch(true) {
case id === 'content_iframe_file': addClass('#content_pane','has_iframe_dir'); break; // iframe_dir
default: addClass('#content_pane','has_dir'); removeClass('#content_pane','has_iframe_dir has_iframe_file'); // sidebar dir
}
break;
default: setAttribute('#content_pane','data-content','has_'+ kind); // other content
switch(true) {
case id === 'content_iframe_file': addClass('#content_pane','has_iframe_file'); removeClass('#content_pane','has_dir'); break;
default: removeClass('#content_pane','has_dir has_iframe_dir has_iframe_file');
}
}
addClass('#content_'+ content_el,'has_content');
}
// LINKS, SEARCH PARAMS, AND QUERIES
function makeContentLinkSearchParams(kind) { // ===> GET LINK QUERIES
let query_str, params;
switch(true) {
case kind === 'pdf': query_str = '#view=fitB&scrollbar=1&toolbar=1&navpanes=1'; break; // compose query_str for pdfs
case ( /text|markdown|code/.test(kind) ): // editable text files
params = ['text_editor_split_view','theme','text_editor_sync_scroll']; // define array of required params
query_str = '?'+ makeSearchParams(params).toString() +"&text_editor_theme="+ ( getSearchParam('text_editor_theme') === 'default' ? getSearchParam('theme') : getSearchParam('text_editor_theme') === 'light' ? 'light' : 'dark') + "&enable_text_editing="+ ( hasClass('body','enable_text_editing') ? 'true' : 'false' ) +"&text_editor_default_view="+ getSearchParam('text_editor_default_view'); break; // compose query_str
case ( /app|dir/.test(kind) ): // apps and directories
params = ['sort_by','sort_direction','show_details','show_image_thumbnails','show_numbers','use_custom_icons','show_invisibles','hide_ignored_items','ignore_ignored_items','alternate_background','theme','play_all_media']; // define array of params
query_str = '?'+ makeSearchParams(params).toString(); break; // compose query_str
}
return query_str; // return query_str
}
function setContentSources(id, kind, link, content_el) { // ===> SET CONTENT SOURCES
if ( kind === 'font' ) { setContentFontSource(id,false,'',link); } // make source link for content elements; default is link
document.getElementById('content_'+ content_el).setAttribute('src',link); // set source for content element
}
function setContentTitle(link) { //*** kind = dir or files ***// // ===> SET CONTENT TITLE
let title_text = '';
let $selected = document.querySelector('.directory_item.selected'), selected_link, selected_title, content_link;
selected_link = ( $selected !== null ? decodeURIComponentSafe( $selected.querySelector('a').pathname ).trim() : '' ); // selected sidebar item; link = pathname
selected_title = ( $selected !== null ? $selected.querySelector('.directory_item_name_a').innerText : '' ); // title from stored data
let content_link_info = getLinkInfo(link); // actual loaded content src; if iframe, link is from iframe data-content_name = full path of current item w/o queries
content_link = ( content_link_info !== undefined ? decodeURIComponentSafe( content_link_info[0] ).trim() : '' );
switch(true) {
case selected_link !== content_link: title_text = content_link; break; // unsynced iframe items
default: title_text = selected_title; // synced iframe items
}
title_text = title_text.split('/').join('/<wbr>').split('_').join('_<wbr>'); // allow nice line breaks in title
document.querySelector('#content_title span').innerHTML = title_text; // set title text
}
//============================// SUBDIRECTORIES
function openSubdirectory(parent_id) { // ===> OPEN SUDIRECTORY
let $parent = document.getElementById(parent_id), parent_link = $parent.getElementsByTagName('a')[0].href, level = Number($parent.getAttribute('data-level')) + 1, body_id = document.body.id; // define subdir level
let content_iframe_utility_src = parent_link + makeContentLinkSearchParams('dir') + '&parent_id='+ parent_id +'&subdirectory=true&level='+ level +'&body_id='+ body_id; // assemble src link for utility_iframe
setAttribute('#content_iframe_utility','src',content_iframe_utility_src); // set src for utility_iframe (which processes dir & sends it back to top)
$parent.classList.add('dir_list_subdir_loading'); // removed when iframe_utility sends loaded message with subdir data
}
function closeSubdirectory(sub_dir_id) { // ===> CLOSE SUBDIRECTOY
switch(true) {
case sub_dir_id === undefined: // close all subdirectories
if ( document.querySelectorAll('.directory_item.has_subdirectory') !== null ) { closeContent(); } // if subdir exists, closeContent
removeClass('.directory_item.dir','has_subdirectory dir_list_subdir_loading'); // remove has_subdir and subdir_loading classes
document.querySelectorAll('.directory_item[id*="_"]').forEach( el => el.remove() ); // remove any item whose id contains an underscore (= subdirectory row)
break;
default:
document.getElementById(sub_dir_id).classList.remove('has_subdirectory'); // remove has_subdir class
document.querySelectorAll('.directory_item').forEach(function(el) { if ( el.id.startsWith(sub_dir_id + '_') ) { el.remove(); } }); // remove any row whose id begins with subdirectory parent id
}
let classes = {font:'has_fonts',image:'has_images',media:'has_media'};
for ( const key in classes ) { if ( document.querySelector('#directory_list li.'+key) === null ) { removeClass('body',classes[key]); } } // remove subdir only body classes
updateStats();
}
function openCloseSubdirectory(e,id) { // ===> OPEN CLOSE SUBDIRECTORY
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
let $parent = document.getElementById(id);
removeClass('body','has_stats');
switch(true) {
case ( $parent.querySelector('.name') !== null && /\.trashes|\.temporaryitems|\.spotlight-v\d+/.test($parent.querySelector('.name').dataset.name ) ):
$parent.classList.remove('dir_list_subdir_loading'); break;
default:
selectThis(id);
if ( $parent.classList.contains('has_subdirectory') ) { closeSubdirectory( id ); } else { openSubdirectory( id ); }
if ( isTopWindow() ) { focusSidebar(); } else { document.activeElement.blur(); document.getElementsByTagName('body')[0].focus(); }
}
}
//============================//
// ***** IFRAME CONTENT ***** //
function setUpIframeDirectoryUI(link,agent) { // ===> SET UP IFRAME DIR LIST UI, with utility iframe for subdirectories
link = link.split('/').reverse(); let removed = link.splice(1,1); removed.pop();
let parent_link = link.reverse().join('/'), query_str = new URLSearchParams(window.location.search.toString().slice(1)), subdirectory = query_str.get('subdirectory') || null, body_id = query_str.get('body_id');
window.addEventListener('message',receiveMessage,false); // init receive message event listener
switch(true) {
case ( query_str.get('view_directory_source') || query_str.get('is_error') ) === 'true': break; // do nothing when viewing directory source or if error page...
default: { // ...else set up iframe directory:
let iframe_directory, iframe_head = document.getElementsByTagName('head')[0], iframe_body = document.getElementsByTagName('body')[0], iframe_dir_list = '', gecko_styles = '';
let iframe_utility_iframe = '<iframe id="content_iframe_utility" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" style="display:none;"></iframe>';
let body_classes = ['show_details']; // define body classes
for ( let key of query_str.keys() ) { // add various body_classes...
switch(true) {
case ( /\s/g.test( getSearchParam(key)) ): break; // ignore search params with spaces in them (e.g., from "history" "+" signs)
case key === 'sort_direction': body_classes.push(getSearchParam(key)); break; // add sort_direction
case query_str.get(key) === 'true': body_classes.push(key); break; // add body classes for boolean params
case query_str.get(key) !== 'false': body_classes.push(key+'_'+getSearchParam(key)); break; // non-boolean params (theme, sort, sort_direction)
}
}
if ( agent === 'is_gecko' ) { gecko_styles = ('<style id="gecko_style_rules">'+ gecko_style_rules +'</style>'); }
iframe_head.querySelectorAll('style, link[rel="stylesheet"], link[href$="css"]').forEach( el => el.remove() ); // remove any existing directory index styles
iframe_head.insertAdjacentHTML('beforeend',`<style id="iframe_dir_styles">${iframe_dir_styles }</style><style id="sidebar_styles">${ sidebar_styles }</style>
<style id="directory_nav_styles">${ directory_nav_styles }</style><style id="color_and_background_styles">${color_and_background_styles}</style>${gecko_styles}`); // assemble the iframe head
const new_index = makeNewIndex(iframe_body, query_str.get('sort_by'),'',body_id); // make new index
const make_new_index = new_index[0];
const prepped_index = make_new_index[0]; // define prepped index
let additional_classes = (new_index[0][1].trim().split(/\s+/)).concat(body_classes); // define additional body classes
switch(true) { // Assemble content_iframe and utility_iframe content
case subdirectory === 'true': iframe_dir_list = `<div id="directory_list_outer"><ol id="directory_list" class="border_bottom text_color_default">${ prepped_index }</ol></div>`; break;
default:
iframe_directory = Directory_Elements('iframe',parent_link); // create iframe directory elements
iframe_dir_list = iframe_directory.replace(/insert_prepped_index/,prepped_index).replace(/insert_stats/,make_new_index[2]); // assemble iframe directory
iframe_body.removeAttribute('style'); // remove any body inline styles
iframe_body.classList.add(...additional_classes); // add body styles
}
iframe_body.innerHTML = iframe_dir_list; // append iframe_dir_list to iframe
if ( subdirectory === null ) { iframe_body.insertAdjacentHTML('beforeend',iframe_utility_iframe); initIframeEventListeners(); } // don't add utility_iframe to utility_iframe; init iframe event listeners
if ( subdirectory === 'true' ) { sendMessage('top','dir_list_subdir_loaded','',[document.getElementById('directory_list').innerHTML,make_new_index[1]] ); } // send prepped subdir to parent window
}
}
}
function setUpIframeUI(agent) { // ===> SET UP IFRAME UI for dir lists, text files, cuetxt, link files, html files
let message = 'iframe_loaded', iframe_location = decodeURIComponentSafe(window.location.href), content = '';
setAttribute('body','id','iframe_body'); // add iframe body id
let text_files = $item_kind.markdown.concat($item_kind.text, $item_kind.code), link_files = new RegExp('.'+ $item_kind.link.join('$|.') +'\$','m'); // define file types
switch(true) { // determine UI type
case window.location.pathname.indexOf('/?') > 0: case window.location.pathname.endsWith('/'): setUpIframeDirectoryUI(iframe_location,agent); break; // if dir, set up iframe dir list UI
case text_files.includes( window.location.pathname.slice( window.location.pathname.lastIndexOf('.') + 1 ) ): setUpTextEditorUI(); // if text file, set up iframe text editor
if ( iframe_location.endsWith('.cuetxt') ) { content = document.getElementById('text_editor_raw_pane').value; } break; // if .cuetxt file, get text for processing
case ( /\.html|\.htm$/m.test(iframe_location) ): initIframeHtmlFileEventListeners(); content = document.querySelector('html').outerHTML; break; // html files
case ( link_files.test(iframe_location) ): content = document.body.innerText; message = 'link_file_link'; break; // send link file content to top
default: initIframeHtmlFileEventListeners(); break; // all other iframe content (e.g. html)
}
sendMessage('top',message,'',[iframe_location,content]); // send iframe_loaded message (unless link file), with args
}
// IFRAME CLICK AND SELECT FUNCITONS
function iframeSelectItem(row_id) { // ===> IFRAME SELECT ROW
if ( document.querySelector('.directory_item.selected') !== null ) { removeClass('.directory_item.selected','selected'); }
addClass('#'+ row_id,'selected');
if ( document.getElementById(row_id).classList.contains('audio_loaded') ) { sendMessage('top','iframe_play_pause_media'); } // play/pause media
}
function iframeDirectoryClick(e,id) { // ===> IFRAME DIR LIST CLICK
e.preventDefault(); e.stopImmediatePropagation();
removeClass('body','is_blurred');
removeClass('#iframe_body','has_stats');
iframeSelectItem(id);
sendMessage('top','iframe_click');
}
function iframeClickLink(e,link,id) { // ===> IFRAME CLICK LINKS from html files (combine with iframedoubleClickThis()?)
let url, kind;
if ( !link.startsWith('#') ) { e.preventDefault(); url = newURL(link); } // if link is not a link fragment
switch(true) {
case link.startsWith('#'): case url.href.startsWith('file:///?'): case url === undefined: break; // allow default link fragment behavior
case id === 'tbody': e.preventDefault(); window.location = link + '?&view_directory_source=true'; break; //
case id === 'iframe_parent_link': sendMessage('top','show_iframe_parent','',[document.getElementById('iframe_parent_link').href,'dir','iframe_parent']); break; // send message "show_iframe_parent"
case url.protocol === 'file:' && window.location.protocol !== 'file:': sendMessage('top','local_link'); break; // show warning when attempting to open local links from non-local pages
case url.protocol !== 'file:' && window.location.protocol === 'file:': window.open(link,'_blank'); break; // open remote link from local page in new tab/window
case url.protocol === 'file:' && window.location.protocol === 'file:': // nobreak; open local links on local files in iframe
case url.protocol === 'about:': // nobreak; document #link fragments
case RegExp(url.hostname).test(window.location.hostname): // nobreak; same origin links (might not include TLD) (just covering bases)
case RegExp(window.location.hostname).test(url.hostname): // no break; same origin links (might not include TLD) (just covering bases)
kind = getLinkInfo(url.href)[3];
if ( /dir|app/.test(kind) ) { sendMessage('top','show_iframe_dir','',[link,kind] ); } else { sendMessage('top','show_iframe_file','',[link,kind] ); } break;
default: window.open(link,'_blank'); break; // else open external document links in new tab
}
}
function iframedoubleClickThis(e,row_id,href) { // ===> IFRAME DOUBLECLICK THIS iframe dir_list items (files and dirs)
e.preventDefault(); e.stopPropagation();
let kind = document.getElementById(row_id).getAttribute('data-kind'); // get item kind
if ( kind === 'audio' ) {
removeClass('.directory_item.audio','audio_loaded selected');
document.getElementById(row_id).classList.add('audio_loaded','selected'); // iframe audio
}
switch(true) {
case ( /dir|app/.test(kind) ): sendMessage('top','show_iframe_dir','',[href,kind]); break; // iframe dirs: tell top what to open; ignore ignored files when dblclicked
default: sendMessage('top','show_iframe_file','',[href,kind]); // iframe files: tell top what to open; ignore ignored files when dblclicked
}
}
//============================//
// SHOW CONTENT
function showContent(id, args) { // args = [link,kind] // ===> SHOW CONTENT
let row, link_info, link, kind;
if ( getAttribute('#content_pane',['data-loaded']) === 'unloaded' ) { removeAttribute('#content_pane',['data-loaded']); }
switch(true) { // set vars & content_iframe attributes for iframe content or sidebar content
case ( /show_grid|text_editor|show_image_grid|show_font_grid/.test(id) ): break; // ignore these ids (i.e, don't get links)
case id === 'view_directory_source': break; // directory source
case ( /content_iframe_parent|content_iframe_dir|open_sidebar_in_content_pane|open_link_file/.test(id) ): // prep for iframe dirs
addClass('body','focus_content');
link_info = getLinkInfo(args[0]); // get link info
link = link_info[0]; link += makeContentLinkSearchParams('dir'); kind = link_info[3]; // set links for default (chrome) and gecko browsers
addClass('#content_pane','has_iframe_dir'); removeClass('#content_pane','has_iframe_file');
removeAttribute('#content_iframe',['data-iframe_file_src']); // because it's not needed after opening a dir
focusContent(); break;
case id === 'content_iframe_file': // prep for iframe files setContentClasses
addClass('body','focus_content');
link_info = getLinkInfo(args[0]); link = link_info[0]; kind = link_info[3]; // set links for default (chrome) and gecko browsers
switch(true) {
case kind === 'audio': addClass('#content_pane','has_iframe_audio'); break;
default: addClass('#content_pane','has_iframe_file'); setAttribute('#content_iframe','data-iframe_file_src',getAttribute('#content_iframe','src')); // remember iframe file parent directory
} break;
case id === '': link = args[0]; kind = args[1]; break; // id = '' typically when grid items remain after closing subdirectory
case id !== '': // show content from sidebar or iframe items (from above) and get link, kind
row = document.getElementById(id); // link = getFormattedLink(row.guerySelector('a').href,row.dataset.kind);
link_info = getLinkInfo( getAttribute('#'+ id +' a','href') );
link = link_info[0]; kind = link_info[3]; // set links for default (chrome) and gecko browsers
switch(true) {
case kind === 'dir': case kind === 'app': case ( /code|text|markdown/.test(kind) ): // add link queries for dirs and text
link = link + makeContentLinkSearchParams(kind); break;
case ( /text|markdown|code/.test(kind) ): // sendMessage('iframe','get_text_selection','',[row.attr('data-selection_start'),row.attr('data-selection_end')]);
break;
}
break;
}
let content_el = ( ['audio','font','image','pdf','video'].includes(kind) ? kind : 'iframe' );
switch(true) {
case !isTopWindow():
if ( id === 'show_grid' ) { showGrid(); } break; // don't attempt to show content for iframe items when navigating
case row !== undefined && hasClass('#'+ id,'ignored') && getSearchParam('ignore_ignored_items') === 'false': // try to open ignored files if they are not to be ignored
window.location = row.querySelector('a').href; break;
case row !== undefined && hasClass('#'+ id,'ignored') && getSearchParam('ignore_ignored_items') === 'true': // don't open ignored files if they are to be ignored
closeContent(); setContentTitle( getAttribute('.directory_item.selected a','href')); setAttribute('#content_pane','data-content','has_ignored');
break;
case kind === 'link':
closeContent();
addClass('#content_iframe','has_content');
setAttribute('#content_pane','data-loaded','loaded');
setAttribute('#content_iframe','src',link);
setAttribute('#content_iframe','data-kind',kind);
break;
case ( /show_grid|show_image_grid|show_font_grid/.test(id) ): showGrid(id); break; // show grid
case id === 'text_editor' || id === 'show_text_editor': showTextEditor(); break; // show the text editor
case kind === 'audio': showAudio(id,link); break; // show audio
case kind === 'video': showVideo(id,link); // no break; close audio when showing video, test if needed
case ['font','image','pdf','video'].includes(kind):
closeContent(); // Close all open content
setContentSources(id, kind, link, content_el); // Set the src attribute for the content element
setContentClasses(id, kind, content_el); // Add "data=has_<kind>" to content_pane; test id for content_iframe
setContentTitle( getAttribute('#content_container .has_content:not(#content_iframe)','src')); // no break; Set the title for non-iframe content
if ( kind === 'font' ) { initFontPreviewEventListeners(); } // init font preview event listeners
if ( kind === 'image' ) { setImageDimensions(); } break; // set image dimensions
case id === 'view_directory_source': kind = 'view_directory_source'; link = current_location + '?&view_directory_source=true'; // nobreak;
case kind === 'dir' && id !== 'content_iframe_dir': // nobreak; dirs
case kind === 'app' && id !== 'content_iframe_dir': // nobreak; apps
case content_el === 'iframe': // set iframe source and hide until iframe loaded
closeContent(); // Close all open content
addClass('#content_iframe','has_content');
setAttribute('#content_pane','data-loaded','unloaded'); // hide iframe until loaded, show loading spinner
setAttribute('#content_iframe','src',link);
setAttribute('#content_iframe','data-kind',kind); // set iframe source, data-kind
if ( id !== 'view_directory_source' ) { setAttribute('#content_iframe','data-content_name',link_info[0]); }
break;
}
if ( kind !== 'audio' && id !== '' ) { addRemoveClassSiblings('#'+ id,'content_loaded','content_loaded'); } // don't remove content_loaded class for siblings if kind = audio
if ( isTopWindow() && ( hasClass('body','focus_content') || id === 'content_iframe' ) ) { sendMessage('iframe','focus_iframe'); }
}
//============================// AUTOLOAD CONTENT
function autoLoadFile() { // ===> AUTOLOAD FILE: index files or files from the file shortcut list
let $selected = document.querySelector('.directory_item.dir[id="rowid-'+ Number(getSearchParam("selected")) +'"]');
if ( $selected !== null && isTopWindow() && getSearchParams().has('selected') ) {
$selected.classList.add('selected'); // add selected class
} // remove selected searchParam if item not found
switch(true) {
case $selected !== null && $selected.classList.contains('local'): case !isTopWindow(): // do nothing for local files or iframes
break;
case getSearchParams().has('file'): { // load files (from bookmark or url)
let file_name = decodeURIComponentSafe(getSearchParam('file'));
let $file = Array.from(document.querySelectorAll('.directory_item.file')).filter ( el => el.dataset.name === file_name );
if ( $file[0] !== undefined ) { showThis( $file[0].id ); }
removeSearchParam('file');
}
break;
case hasClass('body','has_audio') && getSearchParam('autoload_media') === 'true': // audio; must be above next case
document.querySelector('.directory_item.audio').classList.add('selected'); // add selected class
if ( document.querySelector('.directory_item.audio') !== null ) { showAudio( document.querySelector('.directory_item.audio').id ); } // load audio
autoLoadCoverArt(); // load cover mage
break;
case hasClass('body','has_video') && getSearchParam('autoload_media') === 'true': // video
document.querySelector('.directory_item.video').classList.add('selected'); // add selected class
if ( document.querySelector('.directory_item.video') !== null ) { showThis( document.querySelector('.directory_item.video').id ); } break; // load video (if no audio)
case hasClass('body','has_playlist'): case hasClass('body','has_filelist'): break; // do nothing for playlists and filelists ... but what about autoload media?
case $selected !== null: showContent( $selected.id ); break;
}
if ( $selected !== null ) { showThis( $selected.id); } // click selected history item --> replaces auto-loaded cover art
if ( getSearchParam('autoload_index_files') !== 'false' && document.querySelector('.directory_item.file.htm a[href*="/index."]') !== null ) { // else load index file
showThis( document.querySelector('.directory_item.file.htm a[href*="/index."]').closest('.directory_item').id );
}
if ( $selected !== null ) { scrollThis('#directory_list','.selected'); }
}
function getImageNames() { // ===> GET IMAGE NAMES (for cover art)
let images = document.querySelectorAll('.directory_item.image'), image_names = [], image_name;
for ( let i = 0; i < images.length; i++ ) {
image_name = images[i].dataset.name;
image_name = image_name.slice(0,image_name.lastIndexOf('.') ); // remove extension
image_names.push( {'id':images[i].id,'name':image_name} ); // add id and name
}
return image_names;
}
function getCoverArtID() { // ===> GET COVER ART ID
const cover_names = ['cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes'];
const image_names = getImageNames();
let id, exact_match, match, cover_name;
for ( cover_name of cover_names ) { // test available image names against cover names
exact_match = image_names.filter( el => el.name === cover_name ); // check for exact match (w/o extension)
match = image_names.filter( el => el.name.indexOf(cover_name) > -1 ); // check for partial match
if ( exact_match.length > 0 ) { return id = exact_match[0].id; }
else if ( match.length > 0 ) { return id = match[0].id; }
}
if (id === undefined ) { return id = image_names[0].id; } // if no matches, return first image id
}
function autoLoadCoverArt() { // ===> AUTOLOAD COVER ART if dir contains audio & images
if ( !isTopWindow() || !hasClass('body','has_images') || !hasClass('body','has_audio') ) { return; } // do nothing if no audio or images found
let cover_ID = getCoverArtID();
if ( cover_ID !== undefined ) {
addClass('#'+ cover_ID,'content_loaded');
setAttribute('#content_pane','data-content','has_image');
addClass('#content_image','has_content');
setAttribute( '#content_image','src',getAttribute('#'+ cover_ID +' a','href') );
document.querySelector('#content_title span').innerHTML = decodeURIComponentSafe( document.getElementById(cover_ID).querySelector('a').innerText ); // name
setImageDimensions();
}
let selected_ID = ( getSearchParam('selected').length > 0 ? 'rowid-'+ getSearchParam('selected') : undefined);
if ( selected_ID !== undefined ) { removeClass('.directory_item.media','selected'); document.getElementById(selected_ID).classList.add('selected'); }
}
//============================//
// ===> CLOSE CONTENT (Close button or Cmd/Ctrl + W)
function closeMedia(kind) { // type === audio || video // ===> CLOSE MEDIA
let $media_el = ( kind === 'audio' ? document.getElementById('audio') : document.getElementById('content_video') );
$media_el.pause();
$media_el.removeAttribute('src');
switch(true) {
case kind === 'audio':
document.querySelector('#content_audio_title span').innerHTML = '';
removeClass('body','is_playing is_paused');
removeClass('.directory_item.audio_loaded','audio_loaded');
removeClass('#content_pane','has_audio has_iframe_audio');
removeClass('#content_audio_playlist','has_content');
sendMessage('iframe','close_iframe_audio');
break;
case kind === 'video' && getContentPaneData() === 'has_video':
removeClass('.directory_item.video.content_loaded','content_loaded');
removeClass('#content_video','has_content');
removeAttribute('#content_pane',['data-content']);
break;
}
}
function closePlaylist() { // ===> CLOSE PLAYLIST
closeContent(); closeMedia('audio');
removeClass('body'); // remove all body classes
addClass('body',document.getElementById('directory_list').dataset.data_classes); // restore orignal body classes
document.querySelector('head title').innerText = 'Index of: '+ current_location; // restore window title
document.querySelector('#current_dir_path span').innerHTML = current_dir_path; // restore current_dir_path tilte
document.getElementById('directory_list').innerHTML = document.getElementById('directory_list').dataset.dir_list; // restore original dir_list
if ( document.querySelector('.directory_item.selected') !== null ) { showThis(document.querySelector('.directory_item.selected').id); } // reopen the selected dirlist item
updateStats(); // update stats
initEventListeners() // reinit event listeners
}
function closeIndexSource() { // ===> CLOSE INDEX SOURCE PREVIEW
removeClass('body','has_directory_source');
removeAttribute('#content_pane',['data-content','data-loaded']);
removeAttribute('#content_iframe',['src']);
removeClass('#content_iframe','has_content'); // remove iframe src
document.querySelector('#content_title span').innerHTML = ''; // empty content title
if ( document.querySelector('.directory_item.content_loaded') !== null ) { showThis( document.querySelector('.directory_item.content_loaded').id ); }
focusSidebar();
}
function closeFontFile() { // ===> CLOSE OPENED FONT FILE
document.body.classList.remove('faded','focus_content');
removeAttribute('#content_pane',['data-content']);
removeClass('#content_font','has_content');
document.getElementById('font_file_grid').innerHTML = '';
document.querySelector('#content_title span').innerHTML = '';
document.getElementById('font_file_viewer').remove();
document.getElementById('content_font').font_glyphs = null; // remove glyphs data from $content_font
document.getElementById('content_font').insertAdjacentHTML( 'beforeend', Content_Font_Viewer );
if (document.querySelector('.directory_item.content_loaded') !== null ) { showThis(document.querySelector('.directory_item.content_loaded').id); }
}
function closeGlyph(type) { // ===> CLOSE GLYPH
switch(true) {
case type === 'specimen':
removeAttribute('#font_toolbar',['data-char','data-unicode_hex']);
document.getElementById('specimen_glyph').innerText = '';
break;
default:
document.querySelector('#glyph_viewer').dataset.scale = '1';
document.querySelector('#glyph_viewer svg path').style = '';
setAttribute('#content_pane','data-content','has_font_file');
addClass('#content_font','has_content');
scrollThis('#font_file_grid','.selected');
document.getElementById('glyph_viewer');
}
}
function closeContent() { // Close all .content elements before opening any new .content from sidebar. // ===> CLOSE CONTENT
switch(true) {
case !isTopWindow(): sendMessage('top','close'); break; // close iframe content
default: // remove sources, data-content, styles, and classes from #content_pane
removeAttribute('.directory_item.htm',['data-html_content']);
removeAttribute('#content_title span',['data-after']); // remove content_title data
document.querySelector('#content_title span').innerHTML = ''; // empty content title
hideGrid(); // hide grid
hideTextEditor(true); // hide texteditor
setAttribute('#content_pane','data-content','has_null'); // remove content_pane classes and data attributes
removeClass('#content_pane','has_zoom_image has_scaled_image'); // remove content pane image classes
removeAttribute('#content_pane .has_content',['src','style']); // remove data and style attributes
removeAttribute('#content_pane',['data-loaded']);
removeClass('#content_pane .has_content','has_content'); // remove .content classes
removeAttribute('#content_iframe_utility',['src']); // remove utility iframe source
if ( document.getElementById('font_file_viewer') !== null ) { document.getElementById('font_file_viewer').remove(); } // remove font_file_viewer el
if ( document.getElementById('content_font') !== null ) { document.getElementById('content_font').insertAdjacentHTML( 'beforeend', Content_Font_Viewer ); } // append font_file_viewer --> must reinit eventlisteners?
closeMedia('video'); // close video
removeClass('#cue_sheet_track_list_container_video','has_cue_sheet'); document.getElementById('cue_sheet_track_list_container_video').innerHTML = ''; // close video cue sheet menu
removeClass('.playlist_entry_container','has_content'); document.querySelector('.playlist_entry_container textarea').value = ''; // close playlist_entry_container
setupContentPDF(); // reset content_pdf
}
}
function closeButton() { // ===> CLOSE BUTTON
let $content_pane_data = getContentPaneData();
switch(true) {
case hasClass('#content_pane','has_audio') && $content_pane_data === 'has_null': closeMedia('audio'); focusSidebar(); break; // close audio
case hasClass('body','edited'): showWarning('closeButton','edited'); break; // close edited text editor
case $content_pane_data === 'has_text_editor': hideTextEditor(true); break; // hide text editor
case hasClass('body','iframe_edited'): sendMessage('iframe','unloading','closeContent','iframe_edited'); break; // show close iframe_edited warning
case $content_pane_data === 'has_grid': closeGrid(); break; // close grid
// close files and directories
case hasClass('body','has_directory_source'): closeIndexSource(); break; // close directory source
case hasClass('#content_pane','has_iframe_file') && hasClass('#content_pane','has_iframe_dir'): // close iframe file and reopen navigated container directory
showContent('content_iframe_dir',[ getAttribute('#content_iframe','data-iframe_file_src'),'source_dir'] ); sendMessage('iframe','focus_iframe'); break;
case hasClass('#content_pane','has_iframe_file') && !hasClass('#content_pane','has_iframe_dir'): // close iframe file opened from previewed sidebar dir (not navigated directory) and reopen selected sidebar dir
removeClass('#content_pane','has_iframe_file'); removeAttribute('#content_pane',['data-content']); // no break;
showContent('content_iframe_dir',[ getAttribute('#content_iframe','data-iframe_file_src'),'source_dir'] ); // reopen source directory
case hasClass('#content_pane','has_iframe_dir'): // close navigated iframe directory; reopen selected sidebar dir
removeClass('#content_pane','has_iframe_dir'); showThis( document.querySelector('.directory_item.selected').id ); sendMessage('iframe','focus_iframe'); // click selected sidebar item & focus iframe
break;
case $content_pane_data === 'has_dir': // close selected sidebar directory
removeClass('.directory_item.selected:not(.audio),.directory_item.content_loaded:not(.audio)','selected content_loaded');
removeAttribute('#content_pane',['data-loaded']);
removeAttribute('#content_iframe',['src']); // remove iframe src
removeClass('#content_iframe','has_content');
setAttribute('#content_pane','data-content','has_null');
document.querySelector('#content_title span').innerText = ''; // empty content title has_font
focusSidebar(); break;
// close fonts & glyphs
case $content_pane_data === 'has_glyph': showWarning( 'closeGlyph' ); break; // close glyph
case $content_pane_data === 'has_font_file': showWarning( 'closeFontFile' ); break; // close font file preview
case document.getElementById('specimen_glyph').innerText !== '': removeAttribute('#font_toolbar',['data-char','data-unicode_hex']); document.getElementById('specimen_glyph').innerText = ''; break;
case document.getElementById('font_specimen_grid').innerText !== '': document.getElementById('font_specimen_grid').innerText = ''; document.getElementById('unicode_char_ranges_select').value = '' ; resetFontFeatureSettings(); break;
// close all other content except audio
case $content_pane_data === 'has_ignored': // nobreak; close ignored content
case $content_pane_data !== 'has_null': // nobreak; close all other content
removeClass('.directory_item.selected, .directory_item.content_loaded','content_loaded'); // don't remove selected in order to allow sidebar navigation to continue from closed file
removeAttribute('#content_iframe',['data-iframe_file_src','data-kind']);
closeContent();
focusSidebar(); break;
// close playlists
case hasClass('body','has_playlist'): case hasClass('body','has_filelist'): showWarning( 'closePlaylist',[true] ); break; // close playlist/filelist
}
if ( hasClass('#content_pane','has_hidden_grid') ) { showGrid(); } // show hidden texteditor or grid
}
//============================//
// ===> RESET CONTENT (Reset button or Cmd/Ctrl + R)
function reloadImage() { // ===> RELOAD IMAGE
closeContent();
if ( document.querySelector('.image.selected') !== null || document.querySelector('.image.content_loaded') !== null ) { showThis(document.querySelector('.directory_item.image.content_loaded').id); }
}
function reloadContent() { // ===> RELOAD CONTENT
let $content_pane_data = getContentPaneData();
switch(true) {
case getContentPaneData() === 'has_null' && !hasClass('#content_pane','has_audio'): location.reload(); break; // reload window if no content visible
case hasClass('#content_pane','has_audio'): // nobreak; reset media time
document.getElementById('audio').currentTime = 0; document.getElementById('audio').pause(); // pause audio, reset time to 0
case $content_pane_data === 'has_video':
document.getElementById('content_video').currentTime = 0; document.getElementById('content_video').pause(); // pause video, reset time to 0
break;
}
switch(true) { // reset other content
case $content_pane_data === 'has_text_editor': case hasClass('body','has_playlist'): break; // don't do anything else for audio, video, text editor, playlist content.
case $content_pane_data === 'has_grid': showGrid(); break; // show grid
case $content_pane_data === 'has_glyph': document.querySelector('#glyph_viewer g').removeAttribute('style'); setAttribute('#glyph_viewer','data-scale',1); break;
case $content_pane_data === 'has_font': document.getElementById('content_font').style.fontSize = '1em'; document.getElementById('specimen_glyph').style.fontSize = '64vw';
resetFontFeatureSettings(); removeClass('#font_specimen_grid .selected','selected');
document.getElementById('font_specimen_grid').innerHTML = '';
break; // reset font previews
case $content_pane_data === 'has_image': reloadImage(); break; // reload image
case ( /has_text|has_markdown|has_htm|has_iframe|has_dir/.test( $content_pane_data) ):
switch(true) {
case hasClass('body','iframe_edited'): sendMessage('iframe','reloading','reloadContent'); break;
default: showThis(document.querySelector('.directory_item.content_loaded').id);
}
// if ( hasClass('body','iframe_edited') ) { sendMessage('iframe','reloading','reloadContent'); } else { showThis(document.querySelector('.directory_item.content_loaded').id); } break;
case hasClass('#content_pane','has_audio'): case hasClass('#content_pane','has_video'): break; // don't do anything else for audio, video, text editor, playlist content.
case ( /has_ignored|undefined/.test( $content_pane_data ) ): window.location = window.location.href; break; // reload page
}
delete document.getElementById('content_pane').dataset.loaded; // remove dataset.loaded in case file can't be read by utility iframe
}
//**********************// ===> NAVIGATION
function getNavigationType() { // ===> GET NAVIGATION TYPE
let content_pane_data_content = ( document.getElementById('content_pane') !== null ? document.getElementById('content_pane').dataset.content : 'iframe'), nav_type;
switch(true) {
case document.body.classList.contains('has_open_submenu'): nav_type = '#menu li.has_open_submenu ul'; break; // submenu
case document.body.classList.contains('has_menu'): nav_type = '#menu'; break; // menu
case content_pane_data_content === 'iframe' && document.querySelector('#directory_list') !== null: nav_type = '#directory_list'; break; // iframe dir_list
case ( /has_font_file|has_glyph/.test(content_pane_data_content) ): nav_type = '#font_file_grid'; break; // font file glyphs grid
case document.getElementById('font_specimen_grid').children.length > 0 && hasClass('body','focus_content') && !hasClass('body','has_menu') && document.activeElement.contentEditable !== true:
nav_type = '#font_specimen_grid'; break; // font specimen grid
case ( /has_image|has_font/.test(content_pane_data_content) && hasClass('#content_pane','has_hidden_grid') && hasClass('body','focus_content') ): // grids
case content_pane_data_content === 'has_grid' && hasClass('body','focus_content'): // no break
nav_type = '#content_grid'; break;
default: nav_type = '#directory_list'; // default: dir_list
}
return nav_type; // = selector of container of items to be navigated
}
function getNextNavigatedItem(key,bool) { // ===> GET NEXT NAVIGATED ITEM; bool === true for autoplay media
let nav_type = getNavigationType(), selected_el_kind, navigated_el;
let selected_el = ( bool === true ? ( document.querySelectorAll('.audio_loaded,.media.content_loaded')[0] || document.querySelector('.media.selected') || document.querySelector('.media') ) : document.querySelector(nav_type).querySelector('.selected') ); // Get the currently selected item (if any)
if ( selected_el !== null ) { // If there is a selected item...
selected_el.classList.remove('selected'); // ...remove its selected class
if ( !isTopWindow() ) { removeClass('.content_loaded','content_loaded'); } // for iframe dirs with audio and video files
if ( !/warning_buttons|menu/.test(nav_type) ) {
if ( nav_type === '#content_grid' && document.querySelector('#content_grid .image_grid_item') !== null && document.querySelector('#content_grid .font_grid_item') !== null ) {
selected_el_kind = new RegExp(/image|font/); // get both images and fonts from mixed grids for L/R navigation
} else {
selected_el_kind = new RegExp(selected_el.dataset.kind); // get selected_el kind (for L/R navigation)
}
}
if ( /audio|video/.test(selected_el_kind) && hasClass('body','play_all_media') ) { selected_el_kind = /audio|video/; } // but if play_all_media, get both media kinds
}
let els = Array.from(document.querySelector(nav_type).children).filter( function(el) { // Get navigable elements and filter console
if ( nav_type === '#font_file_grid' || el.offsetWidth > 0 && el.offsetHeight > 0 ) { // only return visible items (or glyphs grid items)
if ( selected_el !== null && /ArrowLeft|ArrowRight/.test(key) && !/warning_buttons|menu/.test(nav_type) ) { // if L/R arrow and not menu or warning, and selected_el !== null...
return selected_el_kind.test(el.dataset.kind) && !el.classList.contains('unchecked'); // ...return all unchecked items of same kind as selected_el
} else {
return true; // else return all items
}
}
});
let selected_el_index = ( selected_el === null ? -1 : els.indexOf(selected_el) ); // get index of selected item from filtered els or -1 if null
switch(true) { // get next navigated element
case hasClass('body','has_directory_source'): // if viewing directory source, arrows will reopen selected sidebar item
if ( document.querySelector('.directory_item.content_loaded') !== null ) { showThis( document.querySelector('.directory_item.content_loaded').id ); } else { showThis( selected_el.id ); }
return;
case key === 'ArrowUp': case key === 'ArrowLeft': // ArrowUp / ArrowDown
switch(true) {
case ( /audio|video/.test(selected_el_kind) && key === 'ArrowLeft' ): navigated_el = mediaLeftRightNavigation(els,selected_el,selected_el_index,key); break; // play prev media
case ( /grid|font_specimen_grid/.test(nav_type) && !hasClass('body','has_menu')):
navigated_el = els[gridNavigation(selected_el_index,els.length,nav_type,key)]; break; // els[grid_item_index];
case ( selected_el === null || ( selected_el_index === 0 && !key === 'ArrowLeft' ) ): navigated_el = els[els.length - 1]; break; // select last if nothing selected
case ( /menu/.test(nav_type) && selected_el.classList.contains('is_submenu') && key === 'ArrowLeft' ): navigated_el = subMenuNavigation(selected_el,key); break; // go to parent menu
case ( selected_el_index === 0 ): navigated_el = els[els.length - 1]; break; // additional case for menus
default: navigated_el = els[selected_el_index - 1]; // default dir_list and menu items
}
break;
case key === 'ArrowDown': case key === 'ArrowRight': // ArrowLest / ArrowRight
switch(true) {
case ( /audio|video/.test(selected_el_kind) && key === 'ArrowRight' ): navigated_el = mediaLeftRightNavigation(els,selected_el,selected_el_index,key); break; // play next media
case ( /grid|font_specimen_grid/.test(nav_type) && !hasClass('body','has_menu') ):
navigated_el = els[gridNavigation(selected_el_index,els.length,nav_type,key)]; break; // els[grid_item_index];
case ( selected_el === null || selected_el_index === els.length - 1 ): navigated_el = els[0]; break; // select first if nothing selected
case ( /menu/.test(nav_type) && selected_el.classList.contains('has_submenu') && key === 'ArrowRight' ): navigated_el = subMenuNavigation(selected_el,key); break; // open submenu
default: navigated_el = els[selected_el_index + 1]; // default dir_list and menu items
}
break;
}
switch(true) { // what to do with navigated element:
case ( /grid|menu|font_specimen_grid/.test(nav_type) ): // for grids and menus
navigated_el.classList.add('selected'); // ...add selected class to navigated_el
switch(true) {
case ( /font_specimen_grid/.test(nav_type) ):
if ( document.getElementById('specimen_glyph').innerText === '' ) { addRemoveClassSiblings(navigated_el.id,'selected'); } else { showFontGlyph(navigated_el.id); }
scrollThis('#font_specimen_grid','.selected'); break; // show navigated font glyph
case ( /has_glyph/.test( getContentPaneData() ) ): showFontGlyph(navigated_el.id); break; // show the navigated font file glyph
case ( /grid/.test(nav_type) ):
if ( document.querySelector('#directory_list .selected') !== null ) { document.querySelector('#directory_list .selected').scrollIntoView({block:"nearest"}); } // scroll dir_list item into view
document.querySelector('#content_pane .selected').scrollIntoView({behavior:"smooth",block:"nearest"}); // scroll grid item into view
break;
} break;
case ( /ArrowUp|ArrowDown/.test(key) && navigated_el.dataset.kind === 'audio' ): selectThis(navigated_el.id); break; // only select audio on U/D arrow
case ( /ArrowLeft|ArrowRight/.test(key) && navigated_el.classList.contains('media') ): showThis(navigated_el.id); // load and play media on L/R arrow
if ( selected_el_index === els.length - 1 && !hasClass('body','loop_media') && bool === true ) { null; } else { playPauseMedia('play'); } // if last item and not loop => just select first item, else play
break;
default: showThis(navigated_el.id); // default: show navigated item
}
}
function gridNavigation(selected_el_index,els_length,nav_type,key) { // ===> GRID NAVIGATION
let grid_col_count, grid_row_count, grid_item_index;
if ( /ArrowUp|ArrowDown/.test(key) ) { // calculate number of grid rows and columns
grid_col_count = ( Math.round( document.querySelector( nav_type ).offsetWidth / document.querySelector( nav_type +' > div').offsetWidth ) ); // number of grid items per row
grid_row_count = Math.floor(els_length / grid_col_count); // number of full grid rows
}
switch(true) {
case key === 'ArrowUp': // ArrowUp
switch(true) {
case selected_el_index === -1: grid_item_index = els_length - 1; break; // if nothing selected
case selected_el_index < grid_col_count: // if selected el is in first grid row...
switch(true) { // ...and if it is in a column to the right of last item in last row get...
case (grid_col_count * grid_row_count) + selected_el_index >= els_length: grid_item_index = selected_el_index + (grid_col_count * (grid_row_count - 1)); break; // last in penultimate col or
default: grid_item_index = selected_el_index + (grid_col_count * grid_row_count); // ...last in last row
}
break;
default: grid_item_index = selected_el_index - grid_col_count; // default: grid_item_index = selected_el_index - length of grid row
} break;
case key === 'ArrowDown': // ArrowDown
switch(true) {
case selected_el_index === -1: grid_item_index = 0; break; // if nothing selected, get first item
case selected_el_index + 1 + grid_col_count > els_length: grid_item_index = ( selected_el_index - (grid_col_count * ( grid_row_count - 1)) ) % grid_col_count; break; // if selected is last in column
default: grid_item_index = selected_el_index + grid_col_count; // default: index = selected_el_index plus the length of the grid row
} break;
case key === 'ArrowLeft': grid_item_index = ( ( selected_el_index === -1 || selected_el_index === 0 ) ? els_length - 1 : selected_el_index - 1 ); break; // if first or nothing selected, get last or prev
case key === 'ArrowRight': grid_item_index = ( ( selected_el_index === -1 || selected_el_index + 1 === els_length ) ? 0 : selected_el_index + 1 ); break; // if last or nothing selected, get first or next
}
// selected class to corresponding dir_list item
if ( !/has_font_file|has_glyph/.test(getContentPaneData()) && nav_type !== '#font_specimen_grid' ) {
removeClass('.directory_item.selected','selected'); removeClass('.directory_item.content_loaded','content_loaded'); // remove classes from dir_list items
if ( document.querySelector('#'+ document.querySelector(nav_type).querySelectorAll('div')[grid_item_index].dataset.id) !== null ) {
document.querySelector('#'+ document.querySelector(nav_type).querySelectorAll('div')[grid_item_index].dataset.id).classList.add('selected'); // select corresponding dir_list item
document.querySelector('.directory_item.selected').scrollIntoView({behavior:"smooth",block:"nearest"}); // scroll corresponding dir_list item into view
}
}
return grid_item_index;
}
function subMenuNavigation(selected_el,key) { // ===> SUBMENU NAVIGATION
let navigated_el;
switch(key) {
case 'ArrowLeft':
document.body.classList.remove('has_open_submenu'); selected_el.classList.remove('selected','selected_submenu_item');
navigated_el = selected_el.parentElement.parentElement; navigated_el.classList.remove('hovered','has_open_submenu');
// let hovered_el = document.querySelector('li:hover');
break;
case 'ArrowRight':
document.body.classList.add('has_open_submenu'); selected_el.classList.add('selected','has_open_submenu','hovered');
navigated_el = selected_el.querySelector('ul li'); navigated_el.classList.add('selected_submenu_item'); break;
}
return navigated_el;
}
function mediaLeftRightNavigation(els,selected_el,selected_el_index,key) { // ===> MEDIA LEFT/RIGHT NAVIGATION (Audio)
let navigated_el, navigated_el_id;
switch(true) {
case hasClass('body','shuffle_media'): // if shuffle play enabled...
navigated_el_id = getNextShuffledItem(); // ...get the next shuffled item id
switch(true) { // but if all shuffled items have been played (i.e., navigated_el_id === ''): if loop, update shufflelist, get next item; else get first item.
case navigated_el_id === '': navigated_el = ( hasClass('body','loop_media') ? ( updateShuffleList(), navigated_el = document.getElementById(getNextShuffledItem()) ) : navigated_el = els[0] ); break;
default: navigated_el = document.getElementById( navigated_el_id ); // else get next item in the shufflelist
}
break;
case !selected_el.classList.contains('audio_loaded') && selected_el.classList.contains('audio') && isTopWindow(): navigated_el = selected_el; break; // if selected audio item not loaded, select it
case key === 'ArrowRight': navigated_el = ( selected_el_index + 1 < els.length ? els[selected_el_index + 1] : navigated_el = els[0] ); break; // if selected not last, select next, else select first
case key === 'ArrowLeft': navigated_el = ( selected_el_index - 1 !== -1 ? els[selected_el_index - 1] : navigated_el = els[els.length - 1] ); break; // if selected not first, select prev item, else last
}
selected_el.classList.remove('audio_loaded','content_loaded'); // deselect currently selected media item class
return navigated_el;
}
function playPrevNextMediaTrack() { // ===> PLAY PREV/NEXT MEDIA TRACK
switch(true) {
case !isTopWindow(): // iframe audio files
document.querySelector('.directory_item.selected.media a').trigger('dblclick'); // select and send message to top
sendMessage('top','iframe_play_pause_media');
break; // show iframe audio
}
}
function navigateWarningButtons(e) { // ===> NAVIGATE WARNING BUTTONS
let $buttons = getVisibleElsBySelector('#warning_buttons button');
let $focused_button = getVisibleElsBySelector('#warning_buttons :focus,#warning_buttons .focus')[0], focused_btn_index = $buttons.indexOf($focused_button);
removeClass('#warning_buttons button','focus');
switch(true) {
case e.shiftKey:
switch(true) {
case $focused_button === null || $buttons.indexOf($focused_button) === 0:
$buttons[$buttons.length - 1].focus(); $buttons[$buttons.length - 1].classList.add('focus'); break; // focus last button
default: $buttons[focused_btn_index - 1].classList.add('focus'); $buttons[focused_btn_index - 1].focus(); // else focus previous button
}
break;
default: // e.Tab
switch(true) {
case $focused_button === null || $buttons.indexOf($focused_button) === $buttons.length - 1:
$buttons[0].focus(); $buttons[0].classList.add('focus'); break; // focus first button
default: $buttons[focused_btn_index + 1].classList.add('focus'); $buttons[focused_btn_index + 1].focus(); // else focus next button
}
}
}
function arrowKeyModifierFunctions(e,id) { // ===> ARROW KEY MODIFIER FUNCTIONS
let args = [e.key];
if ( cmdAltKey(e) && ( /ArrowLeft|ArrowRight|ArrowUp|ArrowDown/.test(e.key) ) ) { return; } else { e.preventDefault(); } // prevents starting audio play when changing tabs; allows browser tab cycling
switch(true) {
case ( /ArrowLeft|ArrowRight/.test(e.key) && ( altKey(e) || altShiftKey(e) ) ): // alt/shift + L/R => mediaSkip(e)
if ( e.shiftKey ) { args.push(30); } else { args.push(10); }
if (!isTopWindow()) { sendMessage('top','mediaSkip','mediaSkip',args); } else { mediaSkip(e); }
break;
case cmdKey(e) && e.key === 'ArrowUp': // Cmd/Ctrl + Up
switch(true) {
case !isTopWindow(): iframeClickLink(e,'','iframe_parent_link'); break; // go to iframe parent
case isTopWindow() && hasClass('body','focus_content') && hasClass('#content_iframe','has_content'):
sendMessage('iframe','open_iframe_parent_dir'); break; // fallback for go to iframe parent in case top is incorrectly focused
default: e.preventDefault(); changeLocation([document.querySelector('#parent_dir_nav a').href]); break; // go to parent (with warning for playlists/fonts/edited text)
}
break;
case cmdKey(e) && e.key === 'ArrowDown': // Cmd/Ctrl + Down
switch(true) {
case document.querySelector('.selected') === null: break; // do nothing if nothing selected
case !isTopWindow() && document.querySelector('#iframe_body #directory_list') !== null && cmdKey(e): iframedoubleClickThis( e,id, document.getElementById(id).querySelector('a').href ); break;
case hasClass('.directory_item.selected','link'): openLinkFile(e); break; // open webloc or url files
case hasClass('.directory_item.selected','playlist'): openPlaylist('','',document.querySelector('.directory_item.selected.playlist').dataset.playlist); break; // open playlist or filelist
case isTopWindow() && hasClass('body','focus_content'): focusContent('content_iframe',e); break; // select first item if nothing selected in iframe
case isTopWindow() && hasClass('.directory_item.selected','file') && !hasClass('.directory_item.selected','link'): break; // ? do nothing for link files
case isTopWindow() && hasClass('.directory_item.selected','dir') && hasClass('.directory_item.selected','app') && $settings.apps_as_dirs === false: break; // break if not viewing apps as dirs
default: dblclick( document.querySelector('.directory_item.selected'),showWarning('doubleClickThis', [getAttribute('.directory_item.selected','id'), getAttribute('.directory_item.selected a','href')] ) );
// else double-click directories and all iframe items to open them
}
break;
case cmdKey(e) && e.key === 'ArrowLeft': // Cmd/Ctrl + Left
switch(true) {
case !hasClass('.directory_item.selected','has_subdirectory') && document.querySelector('.directory_item.has_subdirectory') !== null: { // if selected item is in subdirectory...
let $selected = document.querySelector('.directory_item.selected');
while ( !($selected.previousElementSibling.classList.contains('has_subdirectory')) && $selected.previousElementSibling !== null ) { $selected = $selected.previousElementSibling; } // ...find parent
showThis($selected.previousElementSibling.id); // select and show parent dir
}
break;
case hasClass('.directory_item.selected','has_subdirectory'): closeSubdirectory( document.querySelector('.directory_item.dir.selected').id ); break; // close selected subdirectory
default: showThis( getVisibleElsBySelector('.directory_item')[0].id); // select first visible item
}
break;
case cmdKey(e) && e.key === 'ArrowRight': // Cmd/Ctrl + Right
if ( hasClass('.directory_item.selected','dir') && !hasClass('.directory_item.selected','has_subdirectory') ) { openSubdirectory(document.querySelector('.directory_item.dir.selected').id); } // open selected subdirectory
break;
}
}
function arrowKeyNavigation(args) { getNextNavigatedItem(args[0],args[1]); } // args[0] = key, args[1] = bool (for autoplay media) // ===> ARROW KEY NAVIGATION
function arrowKeyFunctions(e,bool,el) { // 'e' = keyboardEvent or string (e.g. 'ArrowLeft/Right' from clickPrevNextButtons() // ===> ARROW KEY FUNCTIONS
let id; if ( el.querySelector('.selected') !== null ) { id = el.querySelector('.selected').id; }
switch(true) {
case ( /a|input|select|textarea|img|embed/.test(document.activeElement.tagName.toLowerCase())) && !cmdKey(e): // no break // needed to allow for normal arrow key function
case ( /text_editor_preview_pane|text_editor_html_pane|content_iframe_pane/.test(document.activeElement.id)) && !cmdKey(e): // no break // " "
case document.activeElement.hasAttribute('contentEditable') && !cmdKey(e): // no break // " "
case !isTopWindow() && document.querySelector('#iframe_body #directory_list') === null && !cmdKey(e): return; // iframe is not a dir_list
case isTopWindow() && hasClass('body','iframe_edited'):
e.preventDefault(); sendMessage('iframe','unloading','',['arrow_key_navigation',e.key]); break;
case isTopWindow() && hasClass('body','focus_content') && hasClass('#content_iframe','has_content'): // needed e.g. after navigating to iframe dir_list parent directory
e.preventDefault(); sendMessage('iframe','iframe_arrow_navigation','',e.key); focusContent('content_iframe',e); break;
case hasClass('body','has_menu') && !isTopWindow(): e.preventDefault(); sendMessage('top','arrow_key_navigation','arrowKeyNavigation',e); break; // menu navigation from focused iframe
case e.altKey: case e.ctrlKey: case e.metaKey: case e.shiftKey: arrowKeyModifierFunctions(e,id); break; // arrow keys + modifiers
default: e.preventDefault(); showWarning( 'arrowKeyNavigation',[e.key,bool] ); // normal L/R/U/D arrow key navigation, with warning
}
}
function clickPrevNextButtons(e,id) { // ===> CLICK PREV/NEXT BUTTONS
e.stopPropagation(); e.preventDefault();
let key = ( /prev_btn|prev_track/.test(id) ? 'ArrowLeft' : /next_btn|next_track/.test(id) ? 'ArrowRight' : null ); // define arrowkey
if ( hasClass('body','focus_content') ) { focusContent(); } else { document.getElementById(id).parentElement.blur(); }
getNextNavigatedItem(key); // get the next track
}
let str = '';
function timeoutID() { return window.setTimeout( function() { str = ''; }, 1500 ); } // ===> TIMEOUT ID: reset typed string to '' after 1.5 sec.
function alphaNav(e) { // ===> ALPHA NAV
switch(true) {
case document.activeElement.tagName.toLowerCase() === 'textarea' || document.activeElement.getAttribute('contentEditable') === true: break;
default:
let timer = timeoutID();
if ( typeof timer === 'number' ) { window.clearTimeout( timer ); timer = 0; } // timeout id
timeoutID();
str += e.key.toLowerCase();
if ( document.querySelector('.directory_item[data-name^="'+ str +'"]') !== null ) { // if matching name found, show the item
showThis(document.querySelector('.directory_item[data-name^="'+ str +'"]').id);
scrollThis('#directory_list','.selected');
// } else {
// null; // replace this with some sort of fuzzy match function? TBD
}
}
}
// ===> END NAVIGATION
//============================//
function selectThis(row_ID) { // ===> SELECT THIS on click and set classes for $content_pane
switch(true) {
// case document.getElementById(row_ID).classList.contains('disabled'): break; // do nothing for disabled items
case document.getElementById(row_ID).classList.contains('audio'): // audio: add/remove selected class
removeClass('.audio','selected'); document.getElementById(row_ID).classList.add('selected'); break;
default: // select dir_list item
removeClass('body','has_directory_source');
addRemoveClassSiblings('#'+ row_ID,'selected','selected content_loaded hovered'); // remove classes from siblings, but leave .audio with playing
}
scrollThis('#directory_list','.selected');
}
function showThis(id) { if ( document.getElementById(id) !== null ) { selectThis(id); showContent(id); } } // ===> SHOW THIS
//============================//
function clickThis(id) { let el = document.getElementById(id); if ( el !== null ) { el.querySelector('a').click(); } else { el.click(); } } // ===> CLICK THIS by id
function doubleClickThis(args) { // ===> DOUBLE-CLICK THIS (dirs only)
let row = document.getElementById(args[0]);
if ( row.classList.contains('dir') && row.classList.contains('invisible') && row.classList.contains('ignored') ) { return; } // don't attempt to open ignored invisible dirs (chiefly system dirs)
let id = args[0].slice(args[0].indexOf('-') + 1), href = args[1];
let query_str = decodeURIComponentSafe(window.location.search);
if ( query_str === '' ) { query_str = '?'; }
if ( query_str.indexOf('history') !== -1 ) {
query_str = query_str.replace(/&selected=\d+/,'').replace(/&history=/,'&history='+ id +'+');
} else {
query_str = query_str.replace(/&selected=\d+/,'') + '&history='+ id;
}
window.location = href + query_str;
}
function clickDirListItem(e,id) { // ===> CLICK DIR LIST ITEM
e.preventDefault(); //e.stopPropagation();
if ( !isTopWindow() ) { return false; } // do nothing for iframe items
focusSidebar(); // focus sidebar
let el = document.getElementById(id);
switch(true) { // show content or play/pause media
case el.classList.contains('audio'): if ( el.classList.contains('selected') ) { playPauseMedia(); } else { showContent(id); } break;
case el.classList.contains('video'): if ( el.classList.contains('selected') ) { playPauseMedia(); } else { showWarning( 'showThis',[id] ); } break;
default:
switch(true) {
case hasClass('body','iframe_edited'): // if iframe edited && clicked item is not
if ( !/image|pdf|font|media/.test(el.className) ) { sendMessage('iframe','unloading','',['showThis',id]); } else { showWarning( 'showThis',[id] ); } break;
default: showWarning( 'showThis',[id] );
}
// if ( hasClass('body','iframe_edited') ) { sendMessage('iframe','unloading','',['showThis',id]); } else { showWarning( 'showThis',[id] ); }
}
}
//============================//
// ===> TEXT EDITING
function setUpTextEditorUI() { // ===> SETUP TEXT EDITOR
let raw_markdown, body_classes = [], content;
if ( !hasClass('body','has_text_editor_UI') ) { // add classes, styles, and scripts; only add once
document.querySelector('head').insertAdjacentHTML('beforeend','<style id="text_editor_styles">'+ text_editor_styles +'</style>');
document.querySelector('head').insertAdjacentHTML('beforeend','<link id="github_markdown_css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>');
if ( isTopWindow() ) { document.querySelector('#content_title span').innerHTML = ''; }
body_classes.push('has_text_editor_UI', getSearchParam('text_editor_default_view'));
}
switch(true) { // get source text and append UI elements
case isTopWindow(): // top level text editor
if ( !hasClass('body','has_text_editor_UI') ) { document.getElementById('content_text').insertAdjacentHTML( 'beforeend', Text_Editing_UI_Elements ); } // only add text editor UI if it hasn't been added already
break;
case !isTopWindow(): // iframe text editing UI
window.addEventListener("message", receiveMessage, true); // add event listener for messages from top
document.querySelector('head').insertAdjacentHTML('afterbegin','<meta charset="utf-8" /><meta http-equiv="Content-Type" content="text/plain; charset="utf-8">');
document.querySelector('head').insertAdjacentHTML('beforeend','<style id="text_editor_styles">'+ global_styles +'</style>');
document.querySelector('head').insertAdjacentHTML('beforeend','<style id="color_and_background_styles">'+ color_and_background_styles +'</style>'); // add iframe text editing styles
document.querySelector('head').insertAdjacentHTML('beforeend','<style id="utilities_styles">'+ utilities_styles +'</style>'); // add iframe text editing styles
raw_markdown = decodeURIComponentSafe( document.querySelector('body > pre').innerText ); // get source text and decode Unicode chars.
document.body.innerHTML = '<div id="content_text" class="background_grey_90">'+ Text_Editing_UI_Elements +' </div>'+ Utilities_Elements('text_editor'); // add the UI
document.getElementById('text_editor_raw_pane').value = raw_markdown; // set the source text value
document.getElementById('text_editor_raw_pane').setSelectionRange(0,0); // set the insertion point to the beginning of the text
if ( /^\#EXTM3U/m.test(document.getElementById('text_editor_raw_pane').value.trim()) ) { // playlists and filelists
content = document.getElementById('text_editor_raw_pane').value.trim(); // get m3u.txt file for processing
sendMessage('top','iframe_playlist','',content);
}
break;
}
switch(true) { // assemble text editing body classes
case getSearchParam('enable_text_editing') === 'false' && !isTopWindow(): // text editing disabled
removeClass('body','text_editor_split_view text_editor_preview text_editor_html');
body_classes.push('disable_text_editing text_editor_raw'); // show the source text
document.getElementById('text_editor_raw_pane').disabled = true; // disable textarea editing
break;
default:
if ( getSearchParam('text_editor_split_view') === 'true' ) { body_classes.push('text_editor_split_view'); }
if ( getSearchParam('text_editor_sync_scroll') === 'true' ) { body_classes.push('text_editor_sync_scroll'); document.querySelector('#text_editor_sync_scroll input').checked = true; }
if ( getSearchParam('text_editor_theme') === 'default' )
{ body_classes.push('text_editor_theme_'+ getSearchParam('theme')); } else { body_classes.push( 'text_editor_theme_'+ getSearchParam('text_editor_theme') ); }
}
addClass('body',body_classes.join(' ')); // add text editor body classes
initTextEditorEventListeners(); // init text editor event listeners
focusTextEditorPanes(); // focus text editor panes
TextEditing(); // call text editing functions
}
function TextEditing() { // ===> TEXT EDITING Function: create Markdown Preview
let raw_markdown = ( document.getElementById('text_editor_raw_pane') !== null ? document.getElementById('text_editor_raw_pane').value.toString() : '' );
MDmarkdown( raw_markdown, document.getElementById('text_editor_html_pane') );
MDsetChecklistClass(); // set checklist class in case any added
}
function focusTextEditorPanes() { // ===> FOCUS TEXT EDITOR PANES
switch(true) {
case hasClass('body','text_editor_split_view'): case hasClass('body','text_editor_raw'): document.getElementById('text_editor_raw_pane').focus(); break;
case hasClass('body','has_preview_html'): document.getElementById('text_editor_html_pane').focus(); break;
case hasClass('body','text_editor_preview'): document.getElementById('text_editor_preview_pane').focus(); break;
}
}
function toggleTextEditorPanes() { // ===> TOGGLE TEXT EDITOR PANES (on tab)
switch(true) {
case document.activeElement.id === 'text_editor_preview_pane' && getFocusableEls('#text_editor_preview_pane').length > 0: // focus focusable elements in text preview
getFocusableEls('#text_editor_preview_pane')[0].focus(); break;
case ( /text_editor_preview_pane|text_editor_html_pane|text_editor_raw_pane]/.test(document.activeElement.id) && !hasClass('body','text_editor_split_view') ): // text editor: if not split view, focus sidebar
sendMessage('top','focus_sidebar'); break;
case hasClass('body','text_editor_split_view'):
switch(true) {
case document.activeElement.id === 'text_editor_raw_pane': // text editor: if text source has focus with split, focus the other pane
if ( hasClass('body','text_editor_html') ) { document.getElementById('text_editor_html_pane').focus(); } else { document.getElementById('text_editor_preview_pane').focus(); } break;
case ( /text_editor_preview_pane|text_editor_html_pane/.test(document.activeElement.id) ): // text editor: if text preview has focus with split, focus text source
document.getElementById('text_editor_raw_pane').focus(); break;
}
break;
}
}
//============================//
// MARKDOWN Functions
function selectTextareaContent(id) { let $textarea = document.getElementById(id); $textarea.focus(); $textarea.select(); $textarea.scrollTop = 0; } // ===> SELECT TEXTAREA CONTENT
function clearText() { // ===> CLEAR TEXT
if ( !isTopWindow() ) { sendMessage('top','iframe_edited'); addClass('body','edited'); }
if ( document.getElementById('text_editor_raw_pane') !== null ) {
document.getElementById('text_editor_raw_pane').value = '';
document.getElementById('text_editor_raw_pane').style.width = '';
document.getElementById('text_editor_raw_pane').focus();
}
removeAttribute('#text_editor_preview_pane',['srcdoc']);
if ( document.getElementById('text_editor_html_pane') !== null ) { document.getElementById('text_editor_html_pane').value = ''; }
}
function saveBtn(id) { // ===> SAVE BUTTON
let data, ext, file_name;
switch(true) {
case getContentPaneData() === 'has_text_editor': file_name = 'untitled'; break;
default: file_name = decodeURIComponentSafe(window.location.pathname.split('/').reverse()[0]);
file_name = file_name.slice(0,file_name.lastIndexOf('.'));
}
switch(true) {
case id === 'save_text': data = document.getElementById('text_editor_raw_pane').value; ext = '.md'; break;
case id === 'save_HTML': data = MDprepHTML( document.getElementById('text_editor_preview_pane').innerHTML ); ext = '.html';
}
saveMD( data, file_name + ext );
}
function MDprepHTML(data) { // ===> MD PREP HTML for saving
const save_HTML_open = `<!DOCTYPE html><html><head><meta charset="utf-8" /><title></title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>
<style></style><script></script></head><body lang="en" class="markdown_body">`;
const save_HTML_close = '</body></html>';
data = data.replace(/<span\sclass="uplink">.<\/span>/g,'');
return save_HTML_open + data + save_HTML_close;
}
function saveMD(data,file_name) { // ===> SAVE MD
if ( !isTopWindow() ) { sendMessage('top','save_text','',[data,file_name]); } else { saveFile(data,'text/plain',file_name); } // #top must save text, else a new window opens containing blob content
removeClass('body,#text_editor_raw_pane,#content_text','edited');
}
// MD Custom pre- and post-processing for text.
function MDaddHeaderIDs(match, p1, p2, p3) { return '<h'+ p1 +' id="'+ p3.toLowerCase().replace(/\s/g,'-') +'" ' + p2 +'>'+ p3; } // ===> MD ADD HEADER IDS (for Toc)
function MDcustomPreProcess(src) { return src; } // ===> MD CUSTOM PREPROCESS (we're not doing anything here just yet...)
function MDcustomPostProcess(html) { // ===> MD CUSTOM POSTPROCESS
html = html.replace(/<(p|li|dt|dd)>-*\s*\[\s*x\s*\]\s*(.+?)<\/(p|li|dt|dd)>$/gm,'<$1 class="checklist"><input type="checkbox" checked><label>$2</label></$3>') // checkboxes in p,li,dt,dd
.replace(/<(p|li|dt|dd)>-*\s*\[\s{1,}\]\s*(.+?)<\/(p|li|dt|dd)>$/gm,'<$1 class="checklist"><input type="checkbox"><label>$2</label></$3>') // checkboxes
// .replace(/<li><p class="checklist">"/g,'<li class="checklist"><p>')
.replace(/^<h(\d)([^>]*)>([^<]+)/gm, MDaddHeaderIDs) // add header IDs;
.replace(/<\/h(\d)>/g,'<span class="uplink">↑</span></h$1>');
return html;
}
function MDmarkdown(raw_markdown) { // ===> MDMARKDOWN: Render markdown from processed source text
const MDit = window.markdownit({linkify:false, typography:false, html:true}).use(window.markdownitMultimdTable, {enableMultilineRows: true})
.use(window.markdownitSub).use(window.markdownitSup).use(window.markdownitFootnote).use(window.markdownitCentertext).use(window.markdownitDeflist).use(window.markdownitTocDoneRight);
let MD_Preview = MDcustomPostProcess( MDit.render( MDcustomPreProcess( raw_markdown ) ) );
let MD_script = '<script id="scroll_script">window.onscroll = function(){ window.parent.postMessage( { "messageContent":"scroll_iframe","functionName":"","arguments":window.scrollY },"*") }; window.onclick = function(e){ e.stopPropagation(); window.parent.postMessage({ "messageContent":"focus_text_preview" },"*" )};</script><style>body{margin:0;padding:0;}</style>'; // inline scripts to permit sync scrolling and focus
document.getElementById('text_editor_preview_pane').srcdoc = MD_script + MD_Preview; // set previewed text
let source_HTML = MD_Preview.toString();
document.getElementById('text_editor_html_pane').value = source_HTML; // set raw html view
}
function MDlivePreview() { MDmarkdown( document.getElementById('text_editor_raw_pane').value ); MDsetChecklistClass(); } // ===> MD LIVE PREVIEW add edited warning
// MD Live Checkboxes prep: find each instance of [ ] or [x] and replace text in index = to clicked checkbox in Preview.
function MDreplaceAt(str, replacement, position) { str = str.substring(0, position) + replacement + str.substring(position + replacement.length); return str; }
function MDreplaceNthSubStr(str,substr,replacement,index) {
let count = 0, found = substr.exec(str);
while ( found !== null ) { if ( count === index ) { return MDreplaceAt(str, replacement, found.index ); } else { count++; found = substr.exec(str); } }
}
function MDliveCheckBoxes(checkbox,$source_el,$preview_el) { // ===> MD LIVE CHECKBOXES
removeClass('.checklist','clicked');
checkbox.closest('p,li,dt,dd').classList.add('clicked');
const this_index = $preview_el.querySelector('.checklist').index( $preview_el.querySelector('.checklist .clicked') );
const src_text = $source_el.value;
const substr = new RegExp(/\[\s*.\s*\]/g);
const replacement = ( checkbox.is(':checked') ? '[x]' : '[ ]' );
$source_el.value = MDreplaceNthSubStr(src_text, substr, replacement, this_index);
}
function MDsetChecklistClass() { // ===> MD SET CHECKBOX LIST CLASS
document.querySelectorAll('#content_text input[type="checkbox"]').forEach( el => el.closest('ul').style.cssText = 'list-style:none;padding:0;' ); // Prevent checkbox lists from having list bullets
}
function MDresizeSplit() { // ===> MD RESIZE SPLIT VIEW
let page_width = window.innerWidth;
let editor_width = document.getElementById('content_text').offsetWidth;
let editor_offsetLeft = ( document.body.id === 'top' ? document.getElementById('content_pane').offsetLeft : 0);
document.onmousemove = function(e) {
e.stopPropagation(); e.preventDefault();
let pageX = e.pageX;
if ( pageX > editor_offsetLeft + 150 && pageX < page_width - 150 ) { // min split pane widths
document.getElementById('text_editing_handle').style.left = pageX - editor_offsetLeft - 4 + 'px';
document.getElementById('text_editor_raw_pane').style.width = pageX - editor_offsetLeft + 'px';
document.getElementById('text_editor_preview_pane').style.width = editor_width + editor_offsetLeft - pageX + 'px';
document.getElementById('text_editor_html_pane').style.width = editor_width + editor_offsetLeft - pageX + 'px';
}
};
}
function MDsyncScroll(e,id,iframe_scrollTop,iframe_scrollHeight) { // ===> MD SYNC SCROLL
if ( !document.querySelector('input[name="text_editor_sync_scroll"').checked || !hasClass('body','text_editor_split_view') ) { return; } // ignore if no split or no sync scroll
// let editor_height = document.getElementById('text_container').offsetHeight;
let scrolled = e.currentTarget, scrolled_scrollTop = iframe_scrollTop || scrolled.scrollTop,
scrolled_height = iframe_scrollHeight || scrolled.scrollHeight,
scrolled_percentage = (scrolled_scrollTop/scrolled_height).toFixed(4);
// the element to be sync scrolled: remove the target element id and the hidden editor pane from the array of editor pane ids
let synced_id = ['text_editor_raw_pane','text_editor_html_pane'].filter(el => el !== scrolled.id).filter(el => document.getElementById(el).offsetHeight > 0).toString();
let synced = document.getElementById(synced_id) || document.getElementById('text_editor_preview_pane').contentWindow.document.documentElement; // the element to be sync scrolled
synced.scrollTo(0, (scrolled_percentage * synced.scrollHeight).toFixed(0), {behavior:'smooth'});
// if ( (scrolled.scrollTop + editor_height) === scrolled_height) { synced.scrollTo(0,synced.scrollHeight, {behavior:'smooth'}); }; // force synced element to bottom
}
function MDtocClick(id) { // ===> MD TOC CLICK anchors
let thisId = document.getElementById(id).href; if ( thisId !== null ) { document.getElementById('text_editor_preview_pane').scrollTop = document.getElementById(id).offset().top - 48; }
}
function MDheaderClick() { // ===> MD HEADER CLICK
switch(true) {
case document.getElementById('text_editor_preview_pane').getElementsByClassName('.table-of-contents') !== null:
document.getElementById('text_editor_preview_pane').getElementsByClassName('table-of-contents')[0].scrollIntoView({behavior:"smooth",block:"nearest"}); break;
default: document.getElementById('text_editor_preview_pane').scroll(0,0);
}
}
//***********************//
// MESSAGES
function sendMessage(target,message,funcName,args) { // ===> SEND MESSAGE to iframe or parent
let messageObj = { 'messageContent': message, 'functionName': funcName, 'arguments': args };
switch(target) {
case 'iframe': document.getElementById('content_iframe').contentWindow.postMessage( messageObj, '*' ); break;
case 'top': window.parent.postMessage( messageObj, '*'); break;
}
}
function receiveMessage(e) { // ===> RECEIVE MESSAGE from iframe or parent, do appropriate action
if ( e.origin === 'null' || e.origin === $origin ) {
let message = e.data.messageContent, args = e.data.arguments;
switch( message ) {
case 'toggleUIPref': toggleUIPref(args); break;
case 'arrow_key_navigation': removeClass('body','iframe_edited'); focusSidebar(); arrowKeyNavigation(args); break; // class_name, key
case 'hide_sidebar': document.body.classList.toggle('hide_sidebar'); break;
case 'toggle_menu': showMenus('menu_container'); break;
case 'close_menu': case 'top_closed_menu': removeClass('body','has_menu is_blurred'); break;
case 'top_has_menu': addClass('#iframe_body','has_menu is_blurred'); break; // tell iframe top has menu
case 'menu_navigation': arrowKeyNavigation(args); break; // menu navigation from iframe
case 'menu_selection': case 'clickMenu': e.preventDefault(); e.stopPropagation(); clickMenu(); break; // show menu
case 'toggle_invisibles': document.querySelector('#show_invisibles_container input').click(); focusContent(); break;
case 'focus_sidebar': focusSidebar(); // no break
case 'escape': case 'tab': // close menus and refocus content or focus sidebar
switch(true) {
case hasClass('#top','focus_content') && hasClass('#top','has_menu'): focusContent(); break;
default: focusSidebar(); scrollThis('#directory_list','.selected'); break;
}
case 'shift_focus_iframe':
switch(true) {
case document.querySelector('#directory_wrapper.iframe') !== null: // if iframe dir_list visible...
removeClass('body','is_blurred');
switch(true) {
case document.querySelectorAll('.directory_item.selected, .directory_item.is_blurred') !== undefined: addClass('.directory_item.is_blurred','selected');
removeClass('.directory_item.is_blurred','is_blurred'); break;
default: getVisibleElsBySelector('.directory_item')[getVisibleElsBySelector('.directory_item').length - 1].classList.add('selected'); // select last row when tabbing into directory
}
break;
default: getFocusableEls('#iframe_body').last().focus(); break;
}
break;
case 'focus_iframe': // after tabbing into iframe
removeClass('body','has_menu is_blurred');
switch(true) {
case document.querySelector('#directory_wrapper.iframe') !== null: // if iframe dir_list visible...
switch(true) {
case document.querySelector('.directory_item.selected, .directory_item.is_blurred') !== null:
addClass('.directory_item.is_blurred','selected'); removeClass('.directory_item.is_blurred','is_blurred'); break;
default: getVisibleElsBySelector('.directory_item')[0].classList.add('selected'); // select first row when tabbing into directory
}
break;
case hasClass('body','has_text_editor_UI'): // if text editor visible...
switch(true) { case document.activeElement.id === 'iframe_body': focusTextEditorPanes(); break; }
switch(true) {
case hasClass('body','has_text_editor_split_view'):
case hasClass('body','text_editor_raw'): {
let selection = window.getSelection();
if ( selection.anchorOffset > 0 ) { // restore cursor position or text selection ( but not for text files )
// document.getElementById('text_editor_raw').setSelectionRange(x,y); // not implemented yet
// document.getElementById('text_editor_raw').scrollTop = [position of x];
// return;
} else {
document.getElementById('text_editor_raw_pane').setSelectionRange(0,0); // set cursor to beginning and scroll to top
document.getElementById('text_editor_raw_pane').scrollTop = 0;
}
break;
}
}
break;
case document.activeElement.id === 'iframe_body': // focus form elements and textareas in iframe files
break;
}
break;
case 'text_editor_theme_default':
addClass('#iframe_body',message); removeClass('#iframe_body','text_editor_theme_light text_editor_theme_dark');
break;
case 'text_editor_theme_light': case 'text_editor_theme_dark':
addClass('#iframe_body',message); removeClass('#iframe_body','text_editor_theme_light text_editor_theme_dark');
break;
case 'theme_light': case 'theme_dark': // toggle iframe UI theme and iframe Text Editor theme
document.getElementById('iframe_body').classList.remove('theme_dark','theme_light','text_editor_theme_light','text_editor_theme_dark');
document.getElementById('iframe_body').classList.add(message,'text_editor_'+ message); break; // change iframe dir theme
case 'blur_iframe': addClass('body','is_blurred'); break;
case 'iframe_click': focusContent(); break; // close menus and fade sidebar
case 'show_iframe_parent': showContent('content_iframe_parent',args); break; // args[0] === item link, args[1] === item kind
case 'show_iframe_dir': showContent('content_iframe_dir',args); break; // args[0] === item link, args[1] === item kind
case 'show_iframe_file': showContent('content_iframe_file',args); break; // args[0] === item link, args[1] === item kind
case 'show_content': // mainly for opening webloc and url files from
if ( args[1] === 'dir' ) { showContent('content_iframe_dir',args); } else { showContent('content_iframe_file',args); }
break;
case 'open_iframe_dir_in_sidebar': window.location = args; break; // tell top to open iframe directory in sidebar; args === iframe directory url
case 'open_iframe_parent_dir': iframeClickLink(e,getAttribute('#iframe_parent_link','href'),'parent'); break; // document.getElementById('#parent').find('a').click();
case 'iframe_arrow_navigation':
switch(args) {
case 'ArrowUp': showThis(getVisibleElsBySelector('.directory_item')[getVisibleElsBySelector('.directory_item').length - 1].id); break;
case 'ArrowDown': showThis(getVisibleElsBySelector('.directory_item')[0].id); break;
}
break;
case 'close': closeContent(); break; // escape content_iframe and close content
case 'closeContent': closeContent(); removeClass('body','iframe_edited'); focusSidebar(); break; // close edited_iframe text after clicking "Save/Don't Save" buttons
case 'reload': showWarning('reloadContent'); break; // reload content
case 'reloadContent': showThis(document.querySelector('.directory_item.content_loaded').id); removeClass('body','iframe_edited'); break; // reload iframe content after "Save/Don't Save" buttons
case 'showThis': removeClass('body','iframe_edited'); focusSidebar(); showThis(args); break; // top: show clicked/navigated sidebar item after "Save/Don't Save" buttons
case 'show_numbers': case 'show_invisibles': case 'alternate_background': case 'hide_ignored_items': case 'ignore_ignored_items':
document.getElementById('iframe_body').classList.toggle(message); break; // toggle iframe dir_list UI prefs from main menu:
case 'show_image_thumbnails':
console.log('message show_image_thumbnails');
//if ( hasClass('body','show_image_thumbnails') ) { console.log("REMOVE"); document.body.classList.remove('show_image_thumbnails'); } else { console.log("ADD");
showImageThumbnails(); //document.body.classList.add('show_image_thumbnails'); } break; // toggle image thumbnails in iframe
// AUDIO MESSAGES
case 'iframe_play_pause_media': playPauseMedia(); break; // tell top to play/pause audio from iframe click
case 'mediaSkip': mediaSkip(undefined,args); break; // tell top to mediaskip from focused iframe
case 'play_prev_next_iframe_audio': playPrevNextMediaTrack(args); break; // play next iframe track
case 'close_iframe_audio': removeClass('.audio_loaded','audio_loaded'); break;
case 'set_media_duration': setMediaDuration(args[0],args[1],args[2],true); break; // set media durations for subdirectories [id, item_sort_kind, duration]
// TEXT EDITING MESSAGES
case 'iframe_edited': if ( !hasClass('#top','iframe_edited') ) { addClass('#top','iframe_edited'); } break; // let top know iframe text has been edited
case 'scroll_iframe': MDsyncScroll(e,'text_editor_preview_pane',args,document.getElementById('text_editor_preview_pane').contentWindow.document.documentElement.scrollHeight); break;
case 'text_editor_toolbar_button': if ( !isTopWindow() ) { document.body.classList.toggle(args); } else { toggleSearchParam(args); } break;
case 'clear': addClass('body#top','iframe_edited'); break; // add edited class after clearing text from edited iframe file
case 'save_text': saveFile(args[0],'text/plain',args[1]); break;
case 'iframe_text_saved': removeClass('body','iframe_edited'); break;
case 'toggle_text_editor': showTextEditor(); break;
case 'focus_text_preview': document.getElementById('text_editor_preview_pane').focus(); break;
case 'unloading': showWarning('closeContent',args); break; // show unsaved changes warning in iframe
case 'reloading': showWarning('reloadContent'); break;
//case 'save_text_selection': document.querySelector('.text.selected,.code.selected,.markdown.selected').dataset.selection_start = args[0];
// document.querySelector('.text.selected,.code.selected,.markdown.selected').dataset.selection_end = args[1]; break; // from iframe
//case 'get_text_selection': document.getElementById('content_text').data.selection_start = args[0];
// document.getElementById('content_text').data.selection_end = args[1]; break;
// OTHERS
case 'link_file_link':
if ( document.querySelector('.directory_item.link.selected') !== null ) {
document.querySelector('.directory_item.link.selected').dataset.link_file_link = args;
showIframeContent(args);
} break; // add link file text to dir_list.selected
case 'iframe_loaded': // iframe loaded
switch(true) {
case args[0].endsWith('.cuetxt'): processCueSheet(args[1]); break; // cue sheet; cue file name must end with ".cuetxt"
default: showIframeContent(args); // if message received by top, iframe loaded successfully
} break;
case 'dir_list_subdir_loaded': // subdirectory loaded, add the subdirectory to the dir_list, update stats
if ( document.querySelector('.directory_item.dir_list_subdir_loading') === null ) { return; }
document.querySelector('.directory_item.dir_list_subdir_loading').insertAdjacentHTML('afterend',args[0]); // insert subdir items
document.querySelector('.directory_item.dir_list_subdir_loading').classList.add('has_subdirectory'); // add "has_subdirectory" class
document.querySelector('.directory_item.dir_list_subdir_loading').classList.remove('dir_list_subdir_loading'); // remove "loading" class
if ( getSearchParam('show_image_thumbnails') === 'true' ) { showImageThumbnails(); }
updateStats(); // update stats
['has_fonts','has_images','has_media'].forEach( function(subdir_class) { if ( args[1].split(' ').includes(subdir_class) ) { addClass('body',subdir_class) } }); // add new body classes
initDirListEventListeners(); // init dir_list event listeners
initIframeEventListeners(); break;
case 'iframe_playlist': // iframe_playlist
document.querySelectorAll('.directory_item.text').forEach( el => el.removeAttribute('data-playlist'));
removeClass('.directory_item.text','playlist');
if ( document.querySelector('.directory_item.text.selected') !== null ) { document.querySelector('.directory_item.text.selected').dataset.playlist = args; }
addClass('.directory_item.text.selected','playlist'); break;
case 'local_link': showWarning('warning_local_file'); break; // local link warning
case 'setIframePlayerStatus': // for iframe audio playback
if ( args === 'play' ) { removeClass('body','is_paused'); addClass('body','is_playing'); } else { removeClass('body','is_playing'); addClass('body','is_paused'); } break;
}
}
}
// END MESSAGES
//============================//
// WARNINGS
function doFunction(funcName,args) { // ===> DO FUNCTION
var funcDictionary = { 'arrowKeyNavigation':arrowKeyNavigation, 'showThis':showThis, 'doubleClickThis':doubleClickThis, 'null':null, 'clickMenu':clickMenu, 'clickThis':clickThis, 'clearText':clearText,
'closeButton':closeButton, 'closeContent':closeContent, 'closeFontFile':closeFontFile, 'closePlaylist':closePlaylist, 'closeGlyph':closeGlyph, 'mediaSkip':mediaSkip,
'openSidebarInContentPane':openSidebarInContentPane, 'reloadContent':reloadContent, 'setLocation':setLocation, 'showDirectorySource':showDirectorySource, 'toggleUIPref':toggleUIPref, 'openInTextEditor':openInTextEditor, 'makePlaylist':makePlaylist,'changeLocation':changeLocation }; // list of functions to remember and execute after warning button click
return funcName === 'null' ? null : funcDictionary[funcName](args); // return the function and call it with args
}
function openWarning(id,buttonid,funcName,args) { // ===> OPEN WARNING
addClass('body','has_warning'); removeAttribute('#warnings_container',['class']); addClass('#warnings_container',id); focusButton(buttonid);
if ( funcName !== undefined && args !== undefined ) { // store funcName and args to complete after clicking warning button
document.getElementById('warnings_container').dataset.funcname = funcName; document.getElementById('warnings_container').dataset.args = args;
}
}
function showWarning(funcName,args) { // ===> SHOW WARNING
let el = document.getElementById(args);
switch(true) {
// case args.length === 2 && args[1] === 'false': doFunction(funcName,args); break; // ignore toggle theme toggleSearchParam
// case ( /theme|alternate_background|show_image_thumbnails|show_numbers|hide_ignored_items|ignore_ignored_items|autoload_media|autoload_index_files|play_all_media|enable_text_editing/.test( args.toString() ) ): doFunction(funcName,args); break; // ignore toggle theme
case ( /warning_make_playlist/.test(funcName) ): openWarning('warning_make_playlist','warning_btn_ok','makePlaylist',args); break; // make playlist/filelist
case ( /warning_no_playlist/.test(funcName) ): openWarning('warning_no_playlist','warning_btn_ok'); break; // warning if no playlist made
case ( /closePlaylist/.test(funcName) ): // close playlist
switch(true) {
case args !== undefined && args[0] === true: openWarning('warning_close_playlist','warning_btn_cancel'); break; // close playlist button: close playlist and audio
case hasClass('#content_pane','has_audio'): closeMedia('audio'); break; // close_button & cmd-W: close audio first
default: openWarning('warning_close_playlist','warning_btn_cancel'); // then close playlist
}
break;
case ( /showThis|closeContent|reloadContent|closeFontFile|changeLocation|setLocation/.test(funcName) && !hasClass('body','has_playlist') && !hasClass('body','has_filelist') ): // warnings for showing content
switch(true) { // upon receipt of message, show iframe warning message, based on the funcName
case !isTopWindow():
switch(true) {
case args.length === 2: openWarning('unloading','warning_btn_save',args[0],args[1]); break; // show iframe edited warning after clicking sidebar item
default: openWarning('unloading','warning_btn_save',funcName,args);
}
break; // open iframe unloading warning for closeContent or reloadContent
case ( /has_font_file|has_glyph/.test( getContentPaneData() ) ):
openWarning('warning_close_font','warning_btn_cancel',funcName,[args]); break; // warning for open font file
default: doFunction(funcName,[args]); break; // default: perform the requested function
}
break;
case ( !/showThis/.test(funcName) ): // warnings for other functions
switch(true) {
case ( funcName === 'toggleUIPref' && /sort_by_*/.test(args[0]) ): if ( !isTopWindow() ) { doFunction(funcName,args); } break; // warn if sorting—since sorting reloads content—but not iframe dirlists
case getContentPaneData() === 'has_font_file' && funcName !== 'arrowKeyNavigation' && el !== null && !el.classList.contains('ignore_warning'):
openWarning('warning_close_font','warning_btn_cancel','closeFontFile'); break; // warn with open font file and close button.
case hasClass('body','iframe_edited'): sendMessage('iframe','unloading',funcName,args); break; // send unloading message to iframe for closeContent or reloadContent
case hasClass('body','edited') && funcName !== 'toggleUIPref': case funcName === 'clearText':
if ( isTopWindow() ) { removeClass('#content_pane','has_hidden_text_editor');
document.getElementById('content_pane').dataset.content = 'has_text_editor';
}
openWarning('clear','warning_btn_save'); break;
default: doFunction(funcName,args); break;
}
}
}
function closeWarning() { removeClass('body','has_warning'); removeClass('#warnings_container, #warning_buttons button'); } // ===> CLOSE WARNINGS
function warningButtons(id) { // ===> WARNING BUTTONs: what to do after warning button click
let funcName = document.getElementById('warnings_container').dataset.funcname || '', args = document.getElementById('warnings_container').dataset.args || '';
switch(id) {
case 'warning_btn_save': case 'warning_btn_dont_save': // Save/Don't Save Buttons
switch(true) { // After clicking Save/Don't Save Button...
case !isTopWindow():
sendMessage('top',document.getElementById('warnings_container').dataset.funcname,document.getElementById('warnings_container').dataset.funcname,[document.getElementById('warnings_container').dataset.args]);
break; // send funcName to top (e.g., showThis)
default:
document.body.classList.remove('iframe_edited'); // remove iframe_edited class
doFunction(funcName,args); // do the function, if any, after clicking don't save button
focusSidebar(); // focus sidebar
}
delete document.getElementById('warnings_container').dataset.funcname; delete document.getElementById('warnings_container').dataset.args; // remove warnings_container data
if (id === 'warning_btn_save') { document.getElementById('save_text_link').click(); } // if id = save button, click save text link
// openInTextEditor(); // why this?
closeWarning();
break;
case 'warning_btn_cancel': closeWarning();
switch(true) {
case isTopWindow():
if ( hasClass('body','focus_content') ) { focusContent(); } else { focusSidebar(); }
break; // Cancel Button
case !isTopWindow():
if ( document.getElementById('warnings_container').dataset.args === 'warning_btn_save' && document.getElementById('warnings_container').dataset.funcname === 'closeContent' ) {
sendMessage('top','closeContent');
}
break;
}
break;
case 'warning_btn_clear': closeWarning(); clearText(); break; // Clear text editor
case 'warning_btn_ok': // OK Button
switch(true) {
case hasClass('#warnings_container','warning_close_font'): closeWarning(); closeFontFile(); break; // close font
case hasClass('#warnings_container','warning_close_playlist'): closeWarning(); closePlaylist(); break; // close playlist
case hasClass('#warnings_container','warning_make_playlist'): closeWarning(); makePlaylist(); break; // make playlist; open no playlist warning if needed
case hasClass('#warnings_container','warning_local_bookmark'): // no break; local bookmark
case hasClass('#warnings_container','warning_local_file'): // no break; local file
case hasClass('#warnings_container','warning_local_playlist'): // no break; local playlist
case hasClass('#warnings_container','warning_no_playlist'): closeWarning(); break; // no playlist
}
doFunction(funcName,args); // do the function, if any, after clicking OK button
break;
}
}
// END WARNINGS
//============================//
// FINIS! † DEO GRATIAS † //
})();