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 † // })();