Supercharged Local Directory File Browser

Makes directory index pages (either local or remote open directories) actually useful. Adds sidebar and preview pane; keyboard navigation; sorting; light/dark UI; browse subdirectories without reloading page; media playback, with shuffle/loop options; support for playlists (m3, extm3u) and cuesheets (.cue); grid view for images/fonts; create, preview, edit, save markdown/plain text files; open font files, view complete glyph repertoire, save individual glyphs as .svg files; more.

Verze ze dne 03. 09. 2021. Zobrazit nejnovější verzi.

  1. /* eslint-disable no-mixed-spaces-and-tabs */
  2. /* eslint-disable no-useless-escape */
  3. /* eslint-disable no-fallthrough */
  4. /* eslint-disable no-case-declarations */
  5. /* eslint-disable indent */
  6. /* eslint-disable quotes */
  7. // ==UserScript==
  8. // @name Supercharged Local Directory File Browser
  9. // @version 6.3.4
  10. // @description Makes directory index pages (either local or remote open directories) actually useful. Adds sidebar and preview pane; keyboard navigation; sorting; light/dark UI; browse subdirectories without reloading page; media playback, with shuffle/loop options; support for playlists (m3, extm3u) and cuesheets (.cue); grid view for images/fonts; create, preview, edit, save markdown/plain text files; open font files, view complete glyph repertoire, save individual glyphs as .svg files; more.
  11. // @author gaspar_schot
  12. // @license GPL-3.0-or-later
  13. // @homepageURL https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser
  14. // @contributionURL https://paypal.me/mschrauzer
  15. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUmRcmZzP8zmf8pVcWPAAAAAXRSTlMAQObYZgAAAFBJREFUeF7tyqERwDAMBEE3mX5UiqDmqwwziTPHjG7xrmzrLFtRaApDIRiKQlMYCsFQFJrCUAiGotAU5hTA1WB4fhkMBsOJwWAwgHvB8CHpBcTbpxy4RZNvAAAAAElFTkSuQmCC
  16. // @include file://*
  17. // @include about:blank
  18. // @include https://www.example.com/path/to/directory/*
  19.  
  20. // TESTING SITES
  21. // @include *michaelschrauzer.com/*
  22.  
  23. // TABLE-TYPE
  24. // @include https://www.caratulas.com/caratulas*
  25. // @include https://cdimage.parrotsec.org/parrot/iso*
  26. // @include https://download.enlightenment.org/re*
  27. // @include https://hypem.com/download/*
  28. // NOT WORKING:
  29. // @include http://mirror.centos.org/centos*
  30. // @include https://ftp.gnome.org/*
  31. // HTTP Security issue: doesn't allow inline styles
  32. // @include http://ftp.gnu.org/*/*
  33.  
  34. // PRE-TYPE
  35. // @include https://download.freenas.org/*
  36. // @include https://nodejs.org/dist*
  37. // @include http://download.dominiosistemas.com.br/*
  38. // @include https://download.epson-europe.com/pub/*
  39. // @include http://voicesinthedark.com/audio/*
  40. // @include http://otrrlibrary.org/OTRRLib/*
  41. // @include http://downloads.it.ox.ac.uk/ota-public*
  42. // NOT WORKING (HTTP issue:)
  43. // @include https://download.gimp.org/pub/gimp*
  44.  
  45. // LIST-TYPE
  46. // @include http://admin.nber.org/pub*
  47. // @include https://ftp.ripe.net/*
  48. // @include https://ftp.ruby-lang.org/*
  49.  
  50. // @require https://code.jquery.com/jquery-latest.min.js
  51. // @require https://cdn.jsdelivr.net/npm/markdown-it@12.0.4/dist/markdown-it.min.js
  52. // @require https://cdn.jsdelivr.net/npm/markdown-it-footnote@3.0.2/dist/markdown-it-footnote.min.js
  53. // @require https://cdn.jsdelivr.net/npm/markdown-it-toc-done-right@2.1.0/dist/markdown-it-toc-made-right.min.js
  54. // @require https://cdn.jsdelivr.net/npm/markdown-it-sub@1.0.0/dist/markdown-it-sub.min.js
  55. // @require https://cdn.jsdelivr.net/npm/markdown-it-sup@1.0.0/dist/markdown-it-sup.min.js
  56. // @require https://cdn.jsdelivr.net/npm/markdown-it-deflist@2.0.3/dist/markdown-it-deflist.min.js
  57. // @require https://cdn.jsdelivr.net/npm/markdown-it-multimd-table@4.0.2/dist/markdown-it-multimd-table.min.js
  58. // @require https://cdn.jsdelivr.net/npm/markdown-it-center-text@1.0.4/dist/markdown-it-center-text.min.js
  59. // @require https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js
  60.  
  61. // UPDATE URL
  62.  
  63. // NOTE: This script was developed in Vivaldi, running on Mac OS Mojave. It has been tested in various Chrome and Gecko-based browsers.
  64. // It has been minimally tested on Windows and not at all on other OSes. It should work, but please report any issues.
  65. // The script does not work on local directories in Safari because Safari does not allow local directories to be browsed, but it will work on remote directories (or on local directories through a local server).
  66.  
  67. // NOTE: By default, Greasemonkey and Tampermonkey will not run scripts on file:/// urls, so for this script to work, you will have to enable it first.
  68. // For Tampermonkey, go to Chrome extension page, and tick the 'Allow access to file URLs' checkbox at the Tampermonkey extension section.
  69. // For Greasemonkey, open about:config and change greasemonkey.fileIsGreaseable to true.
  70.  
  71. // @namespace https://greatest.deepsurf.us/users/16170
  72. // ==/UserScript==
  73.  
  74. (function() {
  75. 'use strict';
  76. const $ = window.jQuery;
  77.  
  78. // ***** USER SETTINGS ***** //
  79. const $settings = {
  80. // NOTE: These settings will be overwritten whenever the script is updated. Use the "Export User Settings" menu item to save them.
  81. // You can paste the exported settings between the two lines below:
  82. //--------------------------------------------------------//
  83.  
  84. bookmarks: // N.B.: Directory links must end with "/", file links must end with another character.
  85. // You may add as many menus and links as you like; just copy the example below and edit as needed.
  86. // Local directory bookmarks must begin with "file:///"; external bookmarks must begin with the correct protocol ("http://" or "ftp://", etc.).
  87. [
  88. // { 'menu_title':'My Sample Menu',
  89. // 'links': [
  90. // { 'link_name':'My Directory Link 1', 'link':'file:///Path/To/My/Directory/' },
  91. // { 'link_name':'My Directory Link 2', 'link':'file:///Path/To/My/Directory_2/' },
  92. // { 'link_name':'My External Link', 'link':'https://www.mywebpage.com/' },
  93. // { 'link_name':'My File Link', 'link':'file:///Path/To/My/File.ext' },
  94. // ]},
  95. { 'menu_title':'My Sample Bookmark Menu',
  96. 'links': [
  97. { 'link_name':'My Directory Link 1', 'link':'file:///Path/To/My/Directory/' },
  98. { 'link_name':'My Directory Link 2', 'link':'file:///Path/To/My/Directory_2/' },
  99. { 'link_name':'My External Link', 'link':'https://www.mywebpage.com/' },
  100. { 'link_name':'My File Link', 'link':'file:///Path/To/My/File.ext' },
  101. ]},
  102. ],
  103. // GENERAL USER SETTINGS
  104. alternate_background: true, // If true (default true), alternate sidebar row background color.
  105. 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.
  106. // If false (default), treat apps as ignored files.
  107. autoload_media: true, // If true (default), the first audio or video file found in a directory will be automatically selected and loaded for playback.
  108. // Also, cover art files (if any) will be loaded in the preview pane.
  109. // Files with 'cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes' in the title will be loaded first in that order, with exact matches having preference.
  110. // 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.
  111. // Otherwise the first image file in directory will be loaded.
  112. autoload_index_files: true, // If true (default: false), automatically select first "index.xxx" (.xxx !== .htm) file found in directory.
  113. // 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.
  114. theme: 'light', // Options: 'light' or 'dark'
  115. sort_by: 'default', // Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
  116. // default = Chrome sorting: dirs on top, files alphabetical.
  117. sort_direction: 'down', // Choose from: 'up' or 'down' (default) (i.e., ascending or descending).
  118. 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").
  119. // 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.)
  120. grid_font_size: 1, // Default = 1
  121. grid_image_size: 184, // Default = 184 (200px - 16px)
  122. show_details: true, // If true (default), hide file and directory details; if false, show them.
  123. hide_ignored_items: false, // If true, ignored files (= files the browser cannot natively open, e.g., common office and graphics files) will be hidden.
  124. // If false (default), ignored files will appear greyed-out.
  125. 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.
  126. // It is recommended to leave the default unchanged.
  127. // Ignored items can still be downloaded with ctrl-click or right-click.
  128. show_invisibles: true, // Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
  129. show_numbers: true, // If true (default true), number index items
  130. UI_font: 'system-ui, sans-serif', // Choose an installed font for the UI; if undefined, use browser defaults instead.
  131. UI_font_size: '13px', // Choose a default UI font size; use any standard CSS units.
  132. use_custom_icons: true, // if true (default), use custom icons for dirs and files
  133. // if false, use browser/server default icons
  134. // TEXT EDITING SETTINGS
  135. enable_text_editing: true, // If true (default), allow plain text files to be edited.
  136. editor_theme: 'default', // Options: 'default' = use "theme" setting, else always 'light' or 'dark'.
  137. default_text_view: 'source_text', // Options: 'source_text','preview_text','preview_html'
  138. // Note that split_view = true overrides this setting.
  139. split_view: false, // If true, show split view on plain text file load.
  140. // if false (default), use default preview_text setting.
  141. sync_scroll: true, // If true (default: true), show split view on plain text file load
  142. // if false, use default preview_text setting.
  143. hide_sidebar: false, //
  144. play_all_media: true // Continuous playback of all media files (i.e., both audio and video), not just selected media type.
  145.  
  146. //--------------------------------------------------------//
  147. // Paste your exported settings between the above two lines.
  148. };
  149.  
  150. // $ITEM_KIND:
  151. // DO NOT DELETE ANY EXISTING CATEGORIES!
  152. // Add file extensions for sorting and custom icon display to the existing categories.
  153. // You can also define your own new categories, but do not add an extension to more than one row_type category.
  154. // Do not add leading "." to the extensions.
  155. const $item_kind = {
  156. // myRowType: ['ext1','ext2'],
  157. dir: ['/'], // loaded in iframe#content_iframe
  158. app: ['app/','app','bat','cgi','com','exe','jar','msi','wsf'], // generally ignored; apps may be opened as directories
  159. alias: ['alias','desktop','directory','lnk','symlink','symlink/'],
  160. archive: ['7z','archive','b6z','bin','bzip','bz2','cbr','dmg','gz','iso','mpkg','pkg','rar','sit','sitx','tar','tar.gz','zip','zipx','zxp'], // ignored
  161. audio: ['aac','aif','aiff','ape','flac','m4a','mka','mp3','ogg','opus','wav'], // loaded in audio#audio
  162. bin: ['a','bundle','dll','dyld','dylib','gem','icc','msi','pyc','pyo','o','rakefile','ri','so','xml','2'], // ignored
  163. code: ['bak','bash','bash_profile','bashrc','c','cfg','cnf','codes','coffee','conf','csh','cshrc','cson','css','cue','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
  164. database: ['accdb','db','dbf','mdb','pdb','sql', 'sqlite','sqlitedb','sqlite3'], // ignored
  165. ebook: ['azw','azw1','azw3','azw4','epub','ibook','kfx','mobi','tpz'], // ignored
  166. font: ['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'], // opened in div#content_font
  167. 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
  168. htm: ['htm','html','xhtm','xhtml'], // opened in iframe#content_iframe
  169. image: ['apng','bmp','gif','ico','jpeg','jpg','png','svg','webp'],
  170. link: ['url','webloc','inetloc'],
  171. markdown: ['md','markdown','mdown','mkdn','mkd','mdwn','mdtxt','mdtext'], // treated as text, opened in iframe#content_iframe text editor
  172. office: ['csv','doc','docx','epub','key','numbers','odf','ods','odt','pages','rtf','scriv','wpd','wps','xlr','xls','xlsx','xlm'], // ignored
  173. pdf: ['pdf'], // open in embed#content_pdf
  174. system: ['DS_Store','ds_store','icon','ics','spotlight-v100/','temporaryitems/','documentrevisions-v100/','trashes/','fseventsd/','dbfseventsd','file','localized','programdata'], // ignored system items
  175. text: ['log','nfo','txt','readme'], // opened in iframe#content_iframe text editor
  176. video: ['m4v','mkv','mov','mp4','mpeg','webm'] // loaded in video#content_video
  177. };
  178. // $ROW_SETTINGS: Ignore or Exclude files by extension
  179. const $row_settings = {
  180. // 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).
  181. ignored: $item_kind.archive.concat( 'alias','m3u','m3u8', $item_kind.bin, $item_kind.database, $item_kind.graphics, $item_kind.ignored_image, $item_kind.office, $item_kind.system)
  182. };
  183. // ***** END USER SETTINGS ***** //
  184.  
  185. // ************ J + M + J ************* //
  186.  
  187. // ************************************ //
  188. // DON'T EDIT ANYTHING BELOW THIS LINE. //
  189. // ************************************ //
  190.  
  191. // 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.
  192. function loadFileURL() {
  193. let search_params = getSearchParams();
  194. search_params.set( 'file', window.location.pathname.split('/').reverse()[0] );
  195. window.location = window.location.pathname.slice( 0,window.location.pathname.lastIndexOf('/') ) +'/?'+ search_params ;
  196. return;
  197. }
  198. if ( !window.location.pathname.endsWith('/') && window.top === window.self ) { loadFileURL(); } // load file urls
  199.  
  200. // ***** GENERAL SETUP ***** //
  201. function getBrowser() {
  202. switch(true) {
  203. case navigator.userAgent.search('Chrome') >= 0: return 'is_chrome';
  204. case navigator.userAgent.search('Firefox') >= 0: return 'is_gecko';
  205. case navigator.userAgent.search('MSIE') >= 0: return 'is_explorer';
  206. case navigator.userAgent.search('Opera') >= 0: return 'is_opera';
  207. case navigator.userAgent.search('Safari') >= 0 && navigator.userAgent.search('Chrome') < 0: return 'is_safari';
  208. }
  209. }
  210. function getOS() { // modded from https://stackoverflow.com/questions/38241480/detect-macos-ios-windows-android-and-linux-os-with-js
  211. var platform = window.navigator.platform, macos_platforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], windows_platforms = ['Win32', 'Win64', 'Windows', 'WinCE'], os = null;
  212. switch(true) {
  213. case macos_platforms.indexOf(platform) !== -1: os = 'macos'; break;
  214. case windows_platforms.indexOf(platform) !== -1: os = 'windows'; break;
  215. // case iosPlatforms.indexOf(platform) !== -1: os = 'ios'; break; // just in case;
  216. // case /Android/.test(userAgent): os = 'android'; break; // just in case;
  217. case !os && /Linux/.test(platform): os = 'linux'; break;
  218. }
  219. return os;
  220. }
  221. // PATHS
  222. const newURL = function(link) {
  223. try { return new URL(link,document.baseURI); }
  224. catch(error) { return; } //console.log('This link is invalid. Please check the file.'); }
  225. };
  226. function decodeURIComponentSafe(str) { // Fix "%" error in file name; see https://stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser
  227. if ( !str ) { return str; }
  228. try {
  229. return decodeURIComponent(str.replace(/%(?![0-9a-fA-F]{2})/g,'%25') ).replace(/\"/g,'\&quot;'); // replace % with %25 if not followed by two a-f/number; replace " with html entity
  230. } catch(e) {
  231. return str;
  232. }
  233. }
  234. const $protocol = window.location.protocol;
  235. const $origin = $protocol +'//'+ window.location.host;
  236. let current_location = decodeURIComponentSafe( [location.protocol, '//', location.host, location.pathname].join('') );
  237. const current_dir_path = current_location.replace(/([/|_|—])/g,'$1<wbr>').replace(/\\/g,'/'); // URL w/o query string for display
  238. const current_dir = current_location.split('/').slice(-2,-1).toString();
  239.  
  240. function escapeStr(str) { str = str.replace(/([$?*+()[]|^])/g,'\\$1'); return str; }
  241. // QUERY PREFS
  242. function getSearchParams() { return new URL(window.location).searchParams; }
  243. // set query key/value
  244. function setSearchParam(key, value) { let search_params = getSearchParams(); search_params.set( key, value ); updateSearchParams(search_params); }
  245. // get query value
  246. function getSearchParam(key) {
  247. let search_params = getSearchParams(), value = '';
  248. switch(true) {
  249. case key === 'width': // set the stored sidebar width or use 30%
  250. value = ( !search_params.has(key) || window.innerWidth === 0 ? 30 : Math.round(100 * Number.parseInt(search_params.get('width'))/window.innerWidth) ); // percentage
  251. break;
  252. default: // if the query_string has a key/value pair, use it, otherwise use the key/value pair from the $settings
  253. value = ( search_params.has(key) ? search_params.get(key) : $settings[key] !== undefined ? $settings[key].toString() : '' );
  254. value = value.replace('%2F','').replace('/',''); // some servers add a '/' to end of query string
  255. }
  256. return value;
  257. }
  258. // toggle query key
  259. function toggleSearchParam(key) { // set search parameter based on key
  260. let search_params = getSearchParams();
  261. let non_bool_prefs = { // 'body_class,data-ui_pref': {'pref_name':'value'}
  262. 'theme_light': {'theme':'light'},
  263. 'theme_dark': {'theme':'dark'},
  264. // 'editor_theme': {'editor_theme':( getSearchParam('editor_theme') === 'default' ? getSearchParam('theme') : getSearchParam('editor_theme') === 'light' ? 'dark' : 'light') },
  265. 'editor_theme': {'editor_theme':(getSearchParam('editor_theme') === 'light' ? 'light' : 'dark') },
  266. 'editor_theme_default': {'editor_theme':getSearchParam('theme') },
  267. 'editor_theme_light': {'editor_theme':'light'},
  268. 'editor_theme_dark': {'editor_theme':'dark'},
  269. 'source_text': {'default_text_view':'source_text'},
  270. 'preview_text': {'default_text_view':'preview_text'},
  271. 'preview_html': {'default_text_view':'preview_html'},
  272. 'sort_by_default': {'sort_by':'default'},
  273. 'sort_by_name': {'sort_by':'name'},
  274. 'sort_by_size': {'sort_by':'size'},
  275. 'sort_by_date': {'sort_by':'date'},
  276. 'sort_by_kind': {'sort_by':'kind'},
  277. 'sort_by_ext': {'sort_by':'ext'},
  278. // 'sort_by_time': {'sort_by':'time'}, // don't add sort by time because times aren't available on initial page load
  279. 'sort_direction_down': {'sort_direction':'down'},
  280. 'sort_direction_up': {'sort_direction':'up'},
  281. };
  282. var value, param_value, settings_value;
  283. switch(true) {
  284. case non_bool_prefs[key] !== undefined:
  285. value = Object.values( non_bool_prefs[key] ).toString(); // get the value for the key
  286. key = Object.keys(non_bool_prefs[key]).toString(); // must come after value: i.e., don't redefine key before getting value
  287. search_params.set( key,value ); // if the setting[key] = value, delete it from search_params; else set query_pref
  288. break;
  289. default: // boolean prefs
  290. settings_value = $settings[key]; // get the default pref value from $settings
  291. param_value = search_params.get(key); // see if pref is set in query prefs
  292. key = key.replace('toggle_','');
  293. value = ( param_value === null ? settings_value.toString() : param_value.toString() ); // if pref is not set in queries, use the default value; else use the query value
  294. value = ( value === 'true' ? 'false' : 'true' ); // toggle value
  295. if ( ( param_value !== null && param_value !== settings_value ) ) { search_params.delete( key ); } else { search_params.set( key,value ); }
  296. }
  297. updateSearchParams(search_params);
  298. }
  299. // remove search param by key
  300. function removeSearchParam(key) { let search_params = getSearchParams(); search_params.delete(key); updateSearchParams(search_params); }
  301. // update search params
  302. function updateSearchParams(search_params) {
  303. let search_params_str = search_params.toString().replace('%2F','').replace('/','');
  304. let new_location = ( search_params_str.length === 0 ? window.location.pathname : window.location.pathname +'?'+ search_params_str ); // don't add ? if no search params
  305. if ( search_params_str.length > -1 ) { window.history.replaceState({}, document.title, new_location); }
  306. updateParentLinks();
  307. }
  308.  
  309. // ***** SET UP UI ELEMENTS ***** //
  310. // Parent and Parents Menus
  311. // UTILITIES
  312. // getElementById alias
  313. function getElById(id) { return $(document.getElementById(id) ); }
  314. function updateParentSearchParams(str) { // decrement selected and history values
  315. let query_str = new URLSearchParams(str); // make new search params from window.location.search
  316. let history = ( query_str.has('history') ? query_str.get('history') : undefined );
  317. if ( history !== undefined ) {
  318. history = history.split(' ');
  319. switch(true) {
  320. case history.length > 1: query_str.set('selected',history[0]); history.shift(); query_str.set('history',history.join('+')); break;
  321. case history.length === 1: query_str.set('selected',history[0]); history.shift(); query_str.delete('history'); break;
  322. }
  323. } else {
  324. query_str.delete('selected');
  325. }
  326. str = decodeURIComponentSafe(query_str.toString());
  327. return str;
  328. }
  329. function updateParentLinks() { $('#parents_dir_nav').siblings('ul').empty().append( createParentLinkItems()[0] ); }
  330. // create links
  331. function createParentLinks() {
  332. let link, links = [], query_str = window.location.search.toString().slice(1);
  333. let link_pieces = current_location.split('/'); // make array of parent directories
  334. link_pieces = link_pieces.slice(2,-2); // remove beginning and ending empty elements and current directory
  335. while ( link_pieces.length > 0 ) { // while there are link pieces...
  336. query_str = updateParentSearchParams(query_str); // update selected and history
  337. link = $protocol +'//'+ link_pieces.join('/') + '/?' + query_str; // assemble link
  338. links.push(link); // add to link array
  339. link_pieces.pop(); // remove last link piece and repeat...
  340. }
  341. return links;
  342. }
  343. // create menu items
  344. function createParentLinkItems() {
  345. let parent_link_menu_items = [], links = createParentLinks();
  346. $('#parent_dir_nav').find('a').attr( 'href', links[0] ); // set parent link
  347. for ( let i = 0; i < links.length; i++ ) {
  348. let display_name = links[i].split('/?')[0];
  349. display_name = display_name.replace(/\//g,'\/<wbr>');
  350. let menu_item = `<li><a href="${ links[i] }" class="text_color_111">${ display_name }/</a></li>`;
  351. parent_link_menu_items.push(menu_item);
  352. }
  353. return [parent_link_menu_items.join(''),(window.location.pathname === '/' ? window.location.href : links[0] )]; // return parents link items
  354. }
  355. // MENUS: User bookmarks
  356. function bookmarksMenuItems() {
  357. const bookmarks = $settings.bookmarks;
  358. let menu_items = [], links_arr = [], links_arr_str = '', links;
  359. if ( bookmarks.length > 0 ) {
  360. for ( let i = 0; i < bookmarks.length; i+=1 ) {
  361. links = bookmarks[i].links;
  362. // make array of links
  363. for ( let j = 0; j < links.length; j+=1 ) {
  364. if ( !links[j].link.endsWith('/') ) {
  365. links[j].link = links[j].link.slice(0,links[j].link.lastIndexOf('/') + 1) + '?file=' + links[j].link.slice(links[j].link.lastIndexOf('/') + 1) ;
  366. }
  367. links[j].link_name = links[j].link_name.split('/').join('/<wbr>');
  368. links_arr[j] = `<li><a class="menu_item has_icon_before text_color_111" href="${ links[j].link }">${ links[j].link_name }</a></li>`;
  369. }
  370. links_arr_str = links_arr.join('');
  371. menu_items[i] = ` <li class="bookmark has_submenu"><a class="menu_item text_color_111">${ bookmarks[i].menu_title }</a>
  372. <ul class="submenu background_color_D0_50 border_all">${ links_arr_str }</ul>
  373. </li>`;
  374. }
  375. menu_items = menu_items.join('');
  376. }
  377. return menu_items;
  378. }
  379. // MENUS: Other menu items
  380. // #menu li a::before, .toggle_UI_pref, a, a:, div, li, span, span::before, span::after, #dir_list a.icon span, #warnings h3::before, #content_audio_title td:before
  381. const sidebar_menu_items = `
  382. <li id="sort_by" class="has_submenu border_top border_bottom"><span class="menu_item">Sort by&hellip;</span> <ul id="sort_menu" class="submenu background_color_D0_50 border_all text_color_111"> <li id="name" class="toggle_UI_pref sorting" data-ui_pref="sort_by_name"><span class="menu_item">Name</span></li> <li id="time" class="toggle_UI_pref sorting" data-ui_pref="sort_by_time"><span class="menu_item">Time (Media Duration)</span></li> <li id="size" class="toggle_UI_pref sorting" data-ui_pref="sort_by_size"><span class="menu_item">Size</span></li> <li id="date" class="toggle_UI_pref sorting" data-ui_pref="sort_by_date"><span class="menu_item">Date</span></li> <li id="kind" class="toggle_UI_pref sorting" data-ui_pref="sort_by_kind"><span class="menu_item">Kind</span></li> <li id="ext" class="toggle_UI_pref sorting" data-ui_pref="sort_by_ext"><span class="menu_item">Extension</span></li> <li id="default" class="toggle_UI_pref sorting" data-ui_pref="sort_by_default"><span class="menu_item">Default</span></li> </ul></li>
  383. <li id="theme_container" title="Set the main UI theme (light or dark)."><span id="theme" class="toggle_UI_pref menu_item" data-ui_pref="theme"><span> Theme</span></span></li>
  384. <li id="alternate_background" class="toggle_UI_pref" data-ui_pref="alternate_background" title="Alternate backgrounds of directory items."><span class="menu_item">Alternate Backgrounds</span></li>
  385. <li id="show_numbers" class="toggle_UI_pref border_bottom" data-ui_pref="show_numbers" title="Number directory list items."><span class="menu_item">Show Numbers</span></li>
  386. <li id="ignored_files" class="has_submenu border_bottom"><span class="menu_item">Ignored Items</span> <ul id="" class="submenu background_color_D0_50 border_all"> <li id="hide_ignored_items" class="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="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>
  387. <li id="autoload_files" class="has_submenu border_bottom"><span class="menu_item">Autoload Files</span> <ul id="autoload_files_menu" class="submenu background_color_D0_50 border_all"> <li id="autoload_media" class="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="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>
  388. <li id="media_files" class="has_submenu border_bottom"><span class="menu_item">Media Files</span> <ul id="media_files_menu" class="submenu background_color_D0_50 border_all"> <li id="play_all_media" class="toggle_UI_pref border_bottom" data-ui_pref="play_all_media" title="Allow continuous playback of all media types, not just the selected media file type."><span class="menu_item">Play All Media Files</span></li> <li id="loop_media_files" class="" title="Loop playback for media files"><span id="loop_media_menu" class="menu_item">Loop Media Playback</span></li> <li id="shuffle_media_files" class="" title="Shuffle media files."><span id="shuffle_media_menu" class="menu_item">Shuffle Media Playback</span></li></ul>
  389. <li id="playlist_options" class="has_submenu border_bottom"><span class="menu_item">Playlists</span> <ul id="playlist_menu" class="submenu background_color_D0_50 border_all"> <li><label id="open_playlist_label" class="menu_item border_bottom" for="open_playlist" title="Open local .m3u playlist/filelist file.">Open Playlist/Filelist&hellip;</label><input type="file" id="open_playlist" name="open_playlist" accept=".m3u,.m3u8"></input></li> <li id="close_playlist_container"><span id="close_playlist" class="menu_item text_color_111" href="#">Close Playlist/Filelist</span></li> <li class=""><span id="make_playlist" class="menu_item text_color_111" href="#" title="Make an .m3u playlist/filelist of the items in the current directory (if any).">Make Playlist/Filelist&hellip;</span></li> </ul></li>
  390. <li class="" 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>
  391. <li id="text_editing" class="has_submenu border_bottom"><span class="menu_item">Text Editing Options</span> <ul id="text_editing_menu" class="submenu background_color_D0_50 border_all"> <li id="text_editor_menu_item" class="border_bottom" title="Toggle the main text editor."><span id="text_editor" class="menu_item">Toggle Text Editor </span></li> <li id="toggle_editor_theme_default" class="border_bottom" title="Editor theme same as the main UI theme."><span id="editor_theme_default" class="toggle_UI_pref menu_item" data-ui_pref="editor_theme_default">Default Text Editor Theme</span></li> <li id="toggle_editor_theme_light" title="Light text editor theme."><span id="editor_theme_light" class="toggle_UI_pref menu_item" data-ui_pref="editor_theme_light" title="Light text editor theme."><span id="editor_theme_light">Light Text Editor Theme</span></li> <li id="toggle_editor_theme_dark" class="border_bottom" title="Dark text editor theme."><span id="editor_theme_dark" class="toggle_UI_pref menu_item" data-ui_pref="editor_theme_dark" title="Dark text editor theme.">Dark Text Editor Theme</span></li> <li id="split_view" class="toggle_UI_pref border_bottom" data-ui_pref="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="source_text" class="toggle_UI_pref menu_item" data-ui_pref="source_text">View Source Markdown/Text</li> <li id="preview_text" class="toggle_UI_pref menu_item" data-ui_pref="preview_text">View Styled Markdown/Text</li> <li id="preview_html" class="toggle_UI_pref menu_item" data-ui_pref="preview_html">View Rendered HTML</li></ul>
  392. <li class="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&hellip;</label><input type="file" id="open_font" name="open_font" accept=".otf,.ttf,.woff"></input></li>
  393. <li id="user_settings" class="has_submenu border_bottom"><span class="menu_item">User Settings</span> <ul id="user_settings_menu" class="submenu background_color_D0_50 border_all"> <li><span id="default_settings" class="menu_item text_color_111 border_bottom" href="#" title="Delete UI prefs stored in the URL query string and reload page.">Reset User Settings</span></li> <li class=""><span id="export_settings" class="menu_item text_color_111" href="#" title="Export hard-coded user settings and bookmarks to text file.">Export User Settings</span></li> </ul></li>
  394. <li id="about"><a class="menu_item text_color_111" href="https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser" target="_blank">About</a></li>
  395. <li id="show_help"><span class="menu_item text_color_111">Help</span></li>
  396. <li id="contact"><a class="menu_item text_color_111" href="mailto:mshroud@protonmail.com">Contact</a></li>
  397. <li id="donate"><a class="menu_item text_color_111" href="https://paypal.me/mschrauzer" target="_blank" rel="noopener">Donate</a></li>
  398. `;
  399. const SidebarHeaderEls = function() {
  400. let checked = '', parent_links = createParentLinkItems();
  401. if ( getSearchParam('show_invisibles') === 'true' ) { checked = 'checked'; }
  402. return `<section id="sidebar_header" class="text_color_111">
  403. <div id="sidebar_title" class="border_bottom background_color_B0_30"><div></div></div>
  404. <div id="sidebar_header_body" class="border_bottom">
  405. <div id="sidebar_menus" class="background_color_B0_30 border_bottom">
  406. <div id="parent_dir_menu"> <nav id="parent_dir_nav" class="invert"><a class="menu_item" href="${ parent_links[1] }" title="Parent Directory"></a></nav> </div>
  407. <div id="parents_dir_menu"> <nav id="parents_dir_nav" class="border_right border_left"> <div id="current_dir_path" title="Parent Directories"><span class="has_icon_before">${ current_dir_path }</span></div> <div id="close_playlist_btn_container" class="border_left" title="Close Playlist"> <div id="close_playlist_btn" class="has_background invert"></div> </div> </nav> <ul id="parents_links" class="menu background_color_D0_50 border_bottom">${ parent_links[0] }</ul> </div>
  408. <div id="menu_container" title="Main menu"> <nav id="menu_nav" class="invert"><div>&nbsp;</div></nav> <ul id="menu" class="menu background_color_D0_50 border_bottom"> ${ bookmarksMenuItems() } ${ sidebar_menu_items } </ul> </div>
  409. </div>
  410. <div id="sidebar_buttons" class="background_color_C0_40 border_bottom">
  411. <div id="sidebar_buttons_left" colspan="3"> <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"><input class="toggle_UI_pref" type="checkbox" id="show_invisibles" data-ui_pref="show_invisibles" for="inv_checkbox" name="inv_checkbox" tabindex="-1"${ checked } /><span class="text_color_111">Show Invisibles</span></label> </div>
  412. <div id="grid_btn" class="has_background" tabindex="-1" title="Show Grid"> <ul class="menu has_popout_menu"> <li id="show_image_grid" class="border_right border_bottom">Show Image Grid</li> <li id="show_font_grid" class="border_right">Show Font Grid</li> </ul> </div>
  413. </div>
  414. <div id="sorting" class="background_color_C0_40">
  415. <div id="sorting_row_1" class="container"> <div id="sort_by_name" class="toggle_UI_pref name sorting" data-ui_pref="sort_by_name" title="Sort by name" colspan="2"><span><input id="play_toggle" type="checkbox" tabindex="-1" checked="true" />Name</span></div> <div id="sort_by_default" class="toggle_UI_pref sorting" data-ui_pref="sort_by_default" title="Default sort" colspan="2"><span>Default</span></div> <div id="sort_by_time" class="toggle_UI_pref sorting" data-ui_pref="sort_by_time" title="Sort by media duration" colspan="2"><span>Time</span></div> </div>
  416. <div id="sorting_row_2"> <div id="sort_by_size" class="toggle_UI_pref details sorting" 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" data-ui_pref="sort_by_date" title="Sort by size"><span>Date</span></div> <div id="sort_by_kind" class="toggle_UI_pref details sorting" data-ui_pref="sort_by_kind" title="Sort by kind"><span>Kind</span></div> <div id="sort_by_ext" class="toggle_UI_pref details sorting" data-ui_pref="sort_by_ext" title="Sort by extension"><span>Ext</span></div> </div>
  417. </div>
  418. <div id="text_editor_row" class="border_top background_color_C0_40"><a href="#" class="text_color_111" title="Toggle Text Editor">Text Editor</a></div>
  419. </div>
  420. </section>`;
  421. };
  422. // Sidebar Footer (Stats)
  423. const sidebar_footer_els = `
  424. <section id="tfoot" class="background_color_D0_50 border_top"> <div id="footer_links" class="has_background invert">&nbsp; <ul class="has_popout_menu invert"> <li id="open_in_content_pane" class="border_bottom text_color_111">Open Sidebar in Content Pane</li> <li id="view_directory_source" class="text_color_111" data-kind="view_directory_source">View Sidebar Directory Source</li> </ul> </div> </section>
  425. `;
  426. // Dir List Elements
  427. const sidebar_dir_list_els = `<section id="dir_list_wrapper" class="border_bottom"><table id="dir_list"> <tbody id="tbody" class="background_color_DD_44 text_color_111" tabindex="0"></tbody> </table></section>`;
  428. // CONTENT PANE ELEMENTS
  429. // Content Audio Els
  430. const content_audio_els = `
  431. <div id="content_audio_title" class="background_color_C0_40" title="Click to toggle .m3u playlist entry."><span></span></div>
  432. <div id="content_audio" class="border_bottom background_color_C0_40">
  433. <div id="audio_container" class="border_all">
  434. <nav id="cue_sheet_track_list_container_audio" class="cue_sheet_track_list_container border_right" title="Cue sheet track list"></nav>
  435. <div id="prev_track" class="prev_next_track_btn audio_controls" title="Previous track">&nbsp;</div>
  436. <div id="next_track" class="prev_next_track_btn audio_controls border_right" title="Next track">&nbsp;</div>
  437. <audio id="audio" preload="auto" tabindex="0" controls controlsList="nofullscreen" >Sorry, your browser does not support HTML5 audio.</audio>
  438. <div id="close_audio" class="audio_controls border_left" title="Close audio"></div>
  439. </div>
  440. <div id="audio_options">
  441. <label id="loop_label"><input type="checkbox" id="loop" for="loop" name="loop" tabindex="0" />Loop</label>
  442. <label id="shuffle_label"><input type="checkbox" id="shuffle" for="shuffle" name="shuffle" tabindex="0" />Shuffle</label>
  443. </div>
  444. </div>
  445. <div id="content_audio_playlist" class="playlist_entry_container border_bottom"><textarea id="content_audio_playlist_textarea" rows="3" spellcheck="false" /></div>
  446. `;
  447. const content_font_viewer = '<div id="font_viewer"><div id="glyphs_container"></div></div>';
  448. // Content Font Els
  449. const ContentFontEls = function() {
  450. const sample_string = `ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789 [(!@#$%^&*;:)]`;
  451. // let sample_string = `ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789.,:;&@!#$%‰\'\"“‘”’()[]{}<>| ¦*/\^-–—_~…<br />ÆÁÂÀÄÃÅÇÐÉÊËÈÍÎÌÏÓÔÒÖÕŒÚÛÙÜÝŸ<br />æáâàäãåçðéêëèíîìïóôòöõœúûùüýÿfiflƒñÑßþÞ<br />†‡£¢¥Øø©®™¿¡«»¯´¶§•ªº¹³²‚„˜˛¸`·¯˘°¨⁄‹›<br />ı½¼¾+±×÷√=≈≠¬∞¤≤≥◊∆∂µ∏π∑Ω<br />■░▒▓│┤╣║╗╝┐└┴┬├─┼╚╔╩╦╠═╬┘┌█▄▀‗`;
  452. 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.`;
  453. return `<div id="font_specimen">
  454. <div id="specimen" class="specimen border_bottom_x" contenteditable="true" tabindex="0">${ sample_string }</div>
  455. <div id="specimen_string_2" class="hamburger border_bottom_x" contenteditable="true" tabindex="0"><h1 id="specimen_2">Typography</h1><h4 id="specimen_2H4">The art of using types to produce impressions on paper, vellum, &amp;c.</h4></div>
  456. <div id="specimen_string_3" class="hamburger border_bottom_x" contenteditable="true"><h2 id="specimen_3">S P E C I M E N</h2><h3 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.</h3></div>
  457. <div id="lorem_strings" 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>
  458. </div>
  459. ${ content_font_viewer }
  460. <div id="glyph_viewer" class="background_color_11_FF invert"><div id="glyph_viewer_info" class="background_color_D0_50 border_bottom_x invert"><button id="save_svg_hidden">Save SVG</button><div></div><button id="save_svg">Save SVG</button></div></div>
  461. `;
  462. };
  463. // Font Viewer
  464. // Iframe Dir Elements
  465. const ContentIframeDirEls = function(parentLink) {
  466. return `<header id="thead">
  467. <div id="change_dirs" class="border_bottom background_color_B0_30 text_color_111"> <span id="parent"><a href="${ parentLink }" class="text_color_111" style="padding-left:16px;" title="Go to parent directory">Parent Directory</a></span> <span id="open_in_sidebar"><a href="" class="text_color_111" style="padding-right:16px;" title="Open this directory in sidebar">Open in Sidebar</a></span> </div>
  468. <div id="sorting_row_1" class="border_bottom text_color_111 background_color_C0_40"> <div id="sort_by_name" class="toggle_UI_pref sorting" data-ui_pref="sort_by_name" title="Sort by name"><span>Name</span></div> <div id="sort_by_default" class="toggle_UI_pref sorting" data-ui_pref="sort_by_default" title="Default sort"><span>Default</span></div> </div>
  469. <div id="sorting_row_2" class="border_bottom background_color_C0_40 text_color_111"> <div id="sort_by_ext" class="toggle_UI_pref sorting" data-ui_pref="sort_by_ext" title="Sort by extension"><span>Ext</span></div> <div id="sort_by_time" class="toggle_UI_pref sorting" data-ui_pref="sort_by_time" title="Sort by duration"><span>Time</span></div> <div id="sort_by_size" class="toggle_UI_pref sorting" data-ui_pref="sort_by_size" title="Sort by size"><span>Size</span></div> <div id="sort_by_date" class="toggle_UI_pref sorting" data-ui_pref="sort_by_date" title="Sort by date"><span>Date</span></div> <div id="sort_by_kind" class="toggle_UI_pref sorting" data-ui_pref="sort_by_kind" title="Sort by kind"><span>Kind</span></div> </div>
  470. </header>
  471. <section id="iframe_dir_list_wrapper" class="background_color_DD_44"><table id="dir_list"><tbody id="tbody" class="border_bottom text_color_111"></tbody></table></section>
  472. <section id="tfoot" class="background_color_D0_50 border_top"></section>`;
  473. };
  474. const content_header_els = `
  475. <header id="content_header">
  476. <div class="text_color_111 background_color_B0_30"><div>
  477. ${ content_audio_els }
  478. <div id="content_title" class="title border_bottom">
  479. <div style="display:inline-flex">
  480. <nav id="cue_sheet_track_list_container_video" class="cue_sheet_track_list_container border_right background_color_B0_30" title="Cue sheet track list"></nav> <div id="title_buttons_left" class="title_left"> <button id="reload_btn" tabindex="-1"><span></span></button> <button id="prev_next_btns" class="split_btn" tabindex="-1"><span id="prev_btn"><span>&nbsp;</span></span><span id="next_btn"><span>&nbsp;</span></span></button> </div>
  481. </div>
  482. <div id="title" class=""><span></span></div>
  483. <div id="title_buttons_right" class="title_right"> <button id="scale" class="split_btn" tabindex="-1"><span id="decrease" title="Enlarge">&nbsp;</span><span id="increase" title="Reduce">&nbsp;</span></button> <button id="open_in_text_editor" title="Open in Text Editor" tabindex="-1"><span>Edit</span></button> <button id="close_btn" tabindex="-1" title="Close Content"><span></span></button> </div>
  484. </div>
  485. <div id="content_playlist" class="playlist_entry_container border_bottom"><textarea id="content_playlist_textarea" rows="3" spellcheck="false" /></div>
  486. </div>
  487. </header>`;
  488. // CONTENT containers
  489. const content_els = `
  490. <div id="content_container" class="background_color_EE_22"> <div id="content_grid" data-kind="grid"></div> <div id="content_text" class="background_color_DD_33"></div> <div id="content_font" class="content background_color_FF text_color_111" spellcheck="false" data-kind="font">${ ContentFontEls() }</div> <div id="content_image_container" class="content background_color_FF_11" data-kind="image"><img id="content_image" tabindex="0"/></div> <embed id="content_pdf" class="content" name="plugin" tabindex="0" data-kind="pdf"></embed> <video id="content_video" class="content background_color_FF_11 media" controls data-kind="video">Your browser does not support the video tag.</video> <iframe id="content_iframe" class="content" 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" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" tabindex="0"></iframe> </div>
  491. `;
  492. // UTILITIES Warnings
  493. const warnings = `
  494. <div id="warnings_header" class="text_color_111 background_color_D0_50"><h3 id="warning_header"><span>Warning:</span></h3><h3 id="make_playlist_header"><span>Make Playlist/Filelist (.m3u)</span></h3></div>
  495. <div id="warnings" class="text_color_111 background_color_D0_50">
  496. <div id="warning_close_font" class="warning">Are you sure you want to close the font preview?</div>
  497. <div id="warning_unsaved_text" class="warning">You have unsaved changes.</div>
  498. <div id="warning_clear_text" class="warning">Are you sure you want to clear all your text?</div>
  499. <div id="warning_local_bookmark" class="warning">Can\'t load local items from non-local pages. <br />&emsp;Please use your browser\'s bookmarks instead or enter the URL manually.</div>
  500. <div id="warning_local_file" class="warning">Can\'t load local items from non-local pages.</div>
  501. <div id="warning_close_playlist" class="warning">Are you sure you want to close the playlist?</div>
  502. <div id="warning_local_playlist" class="warning">This playlist contains local files. <br />&emsp;Please reload this playlist from a local page in order to play them.</div>
  503. <div id="warning_no_playlist" class="warning">Cant make playlist: no qualified items found.</div>
  504. <div id="warning_make_playlist" class="warning">
  505. <form id="make_playlist_form" action="#"><fieldset>
  506. <div><input name="make_playlist" type="radio" id="media_files_only" checked><label for="media_files_only">All media files</label></div>
  507. <div class="indent"><input name="make_playlist" type="radio" id="audio_files_only"><label for="audio_files_only">Audio files only</label></div>
  508. <div class="indent"><input name="make_playlist" type="radio" id="video_files_only"><label for="video_files_only">Video files only</label></div>
  509. <div><input name="make_playlist" type="radio" id="all_non_media_files"><label for="all_non_media_files">All non-media items</label></div>
  510. <div><input name="make_playlist" type="radio" id="all_items"><label for="all_items">All items</label></div>
  511. <div class="indent"><input name="make_playlist" type="radio" id="directories_only"><label for="directories_only">Directories only</label></div>
  512. <div class="indent"><input name="make_playlist" type="radio" id="files_only"><label for="files_only">Files only</label></div>
  513. </fieldset></form>
  514. </div>
  515. </div>
  516. <div id="warning_buttons_container" class="background_color_E0_60"> <div id="warning_buttons"> <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>
  517. `;
  518. // UTILITIES: Help Elements
  519. const content_help = `
  520. <header class="title text_color_111 border_bottom_x background_color_B0_30"><span>HELP</span><button id="close_help" class="focus"><span>Close</span></button></header><section><p style="text-align:center; font-weight:bold;"><a href="https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser" target="_blank">SCRIPT HOMEPAGE (openuserjs.org)</a></p>
  521. <table id="content_help" class="background_color_C0_40 text_color_111 border_top_x border_right_x border_left_x"><tbody class=""><tr><td class="kbd_shortcut text_color_111 border_right_x">KEYBOARD SHORTCUTS</td><td class="help_description text_color_111">DESCRIPTION</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&uarr;</kbd> or <kbd class="background_color_E0_60">&darr;</kdb></kbd></td><td class="help_description text_color_111">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 class="background_color_E0_60">return</kbd> to load it.</td></tr>
  522. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&larr;</kbd> or <kbd class="background_color_E0_60">&rarr;</kbd></kbd></td><td class="help_description text_color_111">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.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">&uarr;</kbd></kbd></td><td class="help_description text_color_111">Go to parent directory</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">&darr;</kbd></kbd></td><td class="help_description text_color_111">Open selected sidebar directory</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">&rarr;</kbd></kbd></td><td class="help_description text_color_111">Open selected subdirectory in sidebar.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">&larr;</kbd></kbd></td><td class="help_description text_color_111">Close selected subdirectory in sidebar or jump to parent directory.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8997;</kbd> + <kbd class="background_color_E0_60">&larr;</kbd> or <kbd class="background_color_E0_60">&#8594;</kbd></kbd></td><td class="help_description text_color_111">Skip audio/video ±10s</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8997;</kbd> + <kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60">&larr;</kbd> or <kbd class="background_color_E0_60">&rarr;</kbd></kbd></td><td class="help_description text_color_111">Skip audio/video ±30s</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">Escape</kbd></td><td class="help_description text_color_111">Close menus and help, unfocus textareas and content pane, etc.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">Return</kbd></td><td class="help_description text_color_111">Open selected directory, select file, or pause/play media.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">Space</kbd></td><td class="help_description text_color_111">Pause/Play media files (if media player loaded).</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">Tab</kbd></td><td class="help_description text_color_111">Toggle focus between sidebar and content pane.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">D</kbd></td><td class="help_description text_color_111">Toggle file details (size, date modified, kind) in some index page types.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">E</kbd></td><td class="help_description text_color_111">Toggle main menu.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">E</kbd></td><td class="help_description text_color_111">Show text editor.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">G</kbd></td><td class="help_description text_color_111">Show or reload image or font grids.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">I</kbd></td><td class="help_description text_color_111">Toggle invisible files.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">O</kbd></td><td class="help_description text_color_111">Open selected sidebar item in new window/tab.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">R</kbd></td><td class="help_description text_color_111">Reload grids and previewed content, reset scaled images/fonts, reset media files to beginning.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">W</kbd></td><td class="help_description text_color_111">Close previewed content (doesn't work in all browsers; use close button instead), or close window if no content is being previewed.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60"><</kbd> or <kbd class="background_color_E0_60">></kbd></td><td class="help_description text_color_111">Scale preview items and grids.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">\\</kbd></td><td class="help_description text_color_111">Toggle sidebar.</td></tr> <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">\\</kbd></td><td class="help_description text_color_111">Toggle text editor split view.</td></tr></tbody></table>
  523. <div><strong>MAIN MENU ITEMS (Selected)</strong><dl><dt></dt><dd>Selecting many of the these 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>BOOKMARK MENU</dt><dd>For convenience you can add bookmarks to files or directories to this menu by editing the User Preference in the code.</dd><dd>These items will be over-written when the script is updated, however; to save them, you may wish to "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>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>
  524. <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>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>
  525. <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 +/&ndash;</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>
  526. </div></section>`;
  527. // MD Build UI
  528. const text_editing_UI_els = `
  529. <table id="toolbar"> <tbody><tr><td>
  530. <ul id="toolbar_buttons"> <li id="editor_theme" class="toggle_UI_pref toolbar_icon has_background" data-ui_pref="editor_theme" title="Toggle Editor Theme"></li> <li id="toggle_source_text" class="toggle_UI_pref toolbar_icon has_background" data-ui_pref="source_text" title="Show source"></li> <li id="toggle_preview_text" class="toggle_UI_pref toolbar_icon has_background" data-ui_pref="preview_text" title="Show Preview"></li> <li id="toggle_preview_html" class="toggle_UI_pref toolbar_icon has_background" data-ui_pref="preview_html" title="Show formatted HTML"></li> <li id="toggle_split_view" class="toggle_UI_pref toolbar_icon has_background" data-ui_pref="split_view" title="Toggle Split View"></li> <li id="sync_scroll" class="" data-ui_pref="sync_scroll"><input name="sync_scroll" type="checkbox"><label for="sync_scroll">Sync Scroll</label></li> <li id="save_btn" title=""><ul class="menu has_popout_menu"> <li id="save_text" class="border_right border_bottom" title="Save source text"><span id="save_text_link" target="_blank">Save Source</span></li> <li id="save_HTML" class="border_right" title="Save rendered html"><span id="save_HTML_link" target="_blank">Save HTML</span></li> </ul></li> <li id="clear_text" title="Clear Text">Clear</li> </ul>
  531. </td></tr></tbody> </table>
  532. <div id="text_container"> <textarea id="text_source" class=" text_color_111" tabindex="0"></textarea> <iframe id="text_preview" class=" text_color_111 markdown-body" tabindex="0"></iframe> <textarea id="html_preview" class=" text_color_111" tabindex="0" readonly></textarea>
  533. <div id="text_editing_handle"></div></div>
  534. `;
  535. // ASSEMBLE SIBEBAR & CONTENT PANE ELEMENTS
  536. const MainContent = `
  537. <main id="main_content">
  538. <section id="sidebar_wrapper" class="border_right" style="width:${ Number(getSearchParam("width")) }%"> ${ SidebarHeaderEls() } <div id="sidebar" class="background_color_C0_40">${ sidebar_dir_list_els } ${ sidebar_footer_els }</div> <div id="handle"></div> <div id="hide_sidebar" class="toggle_UI_pref invert" data-ui_pref="hide_sidebar" title="Toggle Sidebar"></div> </section>
  539. <section id="content_pane" style="width:${ ( 100 - Number(getSearchParam("width")) ) }%">${ content_header_els } ${ content_els }</section>
  540. <section id="utilities"> <div id="warnings_container" class="background_color_E0_60">${ warnings }</div> <div id="help_container" class="background_color_E0_60">${ content_help }</div> </section>
  541. </main>
  542. `;
  543. // DEFINE Content Elements
  544. const $main_content = $(MainContent);
  545. const $dir_list_body = $main_content.find('#tbody');
  546. const $dir_list = $main_content.find('#dir_list');
  547. const $content_pane = $main_content.find('#content_pane');
  548. const $audio_player = $main_content.find('#audio');
  549. const $content_grid = $main_content.find('#content_grid');
  550. const $content_font = $main_content.find('#content_font');
  551. const $content_image_container = $main_content.find('#content_image_container');
  552. const $content_image = $content_image_container.find('img');
  553. const $content_video = $main_content.find('#content_video');
  554. const $content_iframe = $main_content.find('#content_iframe');
  555.  
  556. // SVG UI ICONS
  557. // auto format dark icon colr
  558. function SVG_UI_Icon(icon_name) { return `url("data:image/svg+xml;utf8,${ SVG_UI_Icons[icon_name] }")`; } // get svg UI icon by name
  559. const SVG_UI_Icons = {
  560. 'arrow': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M4 4l12 6-12 6z\' /></svg>',
  561. 'arrow_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23DDDDDD\' d=\'M4 4l12 6-12 6z\' /></svg>',
  562. 'bookmark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4V2zm2 0v15l6-3 6 3V2H4z\' /></svg>',
  563. 'bookmark_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23DDDDDD\' d=\'M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4V2zm2 0v15l6-3 6 3V2H4z\' /></svg>',
  564. 'check_mark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 12 09\'><g transform=\'matrix(0.55,5.55112e-17,-5.55112e-17,0.55,0.578932,-1.01245)\'><path d=\'M-0.071,10.929L2.5,8.358L7,12.857L17.285,2.572L19.856,5.144L7,18L-0.071,10.929Z\' style=\'fill:rgb(68,68,68);fill-rule:nonzero;\'/></g></svg>',
  565. 'chevron_up': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 12 08\'><g transform=\'matrix(1,0,0,1,-3.843,-5.843)\'><path d=\'M10.707,7.05L10,6.343L4.343,12L5.757,13.414L10,9.172L14.243,13.414L15.657,12L10.707,7.05Z\' style=\'fill:rgb(16,16,16);fill-rule:nonzero;\'/></g></svg>',
  566. 'chevron_right': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 08 12\'><g transform=\'matrix(1,0,0,1,-6.086,-4)\'><path d=\'M12.95,10.707L13.657,10L8,4.343L6.586,5.757L10.828,10L6.586,14.243L8,15.657L12.95,10.707Z\' style=\'fill:rgb(16,16,16);fill-rule:nonzero;\'/></g></svg>',
  567. 'chevron_down': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 12 08\'><g transform=\'matrix(1,0,0,1,-4,-6.157)\'><path d=\'M9.293,12.95L10,13.657L15.657,8L14.243,6.586L10,10.828L5.757,6.586L4.343,8L9.293,12.95Z\' style=\'fill:rgb(16,16,16);fill-rule:nonzero;\'/></g></svg>',
  568. 'chevron_left': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 08 12\'><g transform=\'matrix(1,0,0,1,-5.843,-4)\'><path d=\'M7.05,9.293L6.343,10L12,15.657L13.414,14.243L9.172,10L13.414,5.757L12,4.343L7.05,9.293Z\' style=\'fill:rgb(16,16,16);fill-rule:nonzero;\'/></g></svg>',
  569. 'document': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M4 18h12V6h-4V2H4v16zm-2 1V0h12l4 4v16H2v-1z\' /></svg>',
  570. 'error': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23FFB636\' id=\'Layer_2\' d=\'M1.075,18.05l8.146,-16.683c0.236,-0.484 0.924,-0.491 1.169,-0.011l8.537,16.683c0.223,0.435 -0.093,0.952 -0.582,0.952l-16.683,0c-0.483,0 -0.799,-0.507 -0.587,-0.941Z\' style=\'fill-opacity:0.75;fill-rule:nonzero;\'/><path id=\'Layer_3\' d=\'M11.055,7.131l-0.447,6.003c-0.034,0.45 -0.425,0.787 -0.874,0.753c-0.408,-0.03 -0.724,-0.356 -0.753,-0.753l-0.447,-6.003c-0.052,-0.696 0.47,-1.302 1.167,-1.354c0.696,-0.052 1.302,0.47 1.354,1.166c0.005,0.061 0.004,0.129 0,0.188Zm-1.26,8.037c-0.641,0 -1.159,0.518 -1.159,1.158c0,0.641 0.518,1.159 1.159,1.159c0.64,0 1.158,-0.518 1.158,-1.159c0,-0.64 -0.518,-1.158 -1.158,-1.158Z\' style=\'fill:%23444;fill-opacity:0.75;fill-rule:nonzero;\'/></svg>',
  571. 'folder': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M0 4c0-1.1.9-2 2-2h7l2 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm2 2v10h16V6H2z\' /></svg>',
  572. 'grid': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  573. 'grid_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23DDDDDD\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  574. 'grid_loaded': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23118888\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  575. 'grid_loaded_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%2344BBBB\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  576. 'ignored': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M0 10a10 10 0 1 1 20 0 10 10 0 0 1-20 0zm16.32-4.9L5.09 16.31A8 8 0 0 0 16.32 5.09zm-1.41-1.42A8 8 0 0 0 3.68 14.91L14.91 3.68z\' opacity=\'0.25\' /></svg>',
  577. 'ignored_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23BBBBBB\' d=\'M0 10a10 10 0 1 1 20 0 10 10 0 0 1-20 0zm16.32-4.9L5.09 16.31A8 8 0 0 0 16.32 5.09zm-1.41-1.42A8 8 0 0 0 3.68 14.91L14.91 3.68z\' opacity=\'0.25\' /></svg>',
  578. 'menu': '<svg width=\'100%\' height=\'100%\' viewBox=\'0 0 13 10\' version=\'1.1\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' xml:space=\'preserve\' xmlns:serif=\'http://www.serif.com/\' style=\'fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;\'> <rect x=\'0\' y=\'0\' width=\'13\' height=\'2\' style=\'fill:rgb(34,34,34);\'/><rect x=\'0\' y=\'4\' width=\'13\' height=\'2\' style=\'fill:rgb(34,34,34);\'/><rect x=\'0\' y=\'8\' width=\'13\' height=\'2\' style=\'fill:rgb(34,34,34);\'/></svg>',
  579. 'minus': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><rect fill=\'%23222222\' x=\'1\' y=\'8\' width=\'18\' height=\'4\' /></svg>',
  580. 'multiply': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M10,7l6,-6l3,3l-6,6l6,6l-3,3l-6,-6l-6,6l-3,-3l6,-6l-6,-6l3,-3l6,6Z\'/></svg>',
  581. 'music': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23888888\' d=\'M15.987,13.982c0,0.906 -0.413,1.664 -1.239,2.274c-0.757,0.554 -1.604,0.831 -2.541,0.831c-0.548,0 -0.998,-0.129 -1.348,-0.388c-0.389,-0.295 -0.583,-0.708 -0.583,-1.238c0,-0.838 0.398,-1.574 1.192,-2.209c0.752,-0.597 1.559,-0.896 2.421,-0.896c0.727,0 1.257,0.145 1.59,0.434l0,-9.489l-6.755,1.82l0,10.774c0,0.906 -0.413,1.663 -1.238,2.273c-0.758,0.555 -1.605,0.832 -2.541,0.832c-0.549,0 -0.998,-0.13 -1.35,-0.388c-0.388,-0.296 -0.582,-0.709 -0.582,-1.238c0,-0.838 0.398,-1.574 1.192,-2.209c0.752,-0.597 1.559,-0.896 2.421,-0.896c0.727,0 1.257,0.145 1.589,0.434l0,-11.605l7.772,-2.098l0,12.982Z\' style=\'fill-opacity:0.4;fill-rule:nonzero;\'/></svg>',
  582. 'plus': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M8.001,1l3.999,0l0,7l7,0l0,4l-7,0l-0.001,7l-3.999,0l0,-7l-7,0l0,-4l7,0l0.001,-7Z\'/></svg>',
  583. 'prev_next_track': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M13,5l2,0l0,10l-2,0l0,-10Zm-8,0l8,5l-8,5l0,-10Z\'/></svg>',
  584. 'prev_next_track_ff': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23FFFFFF\' d=\'M12.8,14l-10.8,-7l10.8,-7l0,14Z\'\'/><rect x=\'0\' y=\'0\' width=\'2\' height=\'14\' style=\'fill:%23fff;\'/></svg>',
  585. 'spinner': '<?xml version=\'1.0\' encoding=\'utf-8\'?><svg xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' style=\'margin: auto; background: none; display: block; shape-rendering: auto;\' width=\'200px\' height=\'200px\' viewBox=\'0 0 100 100\' preserveAspectRatio=\'xMidYMid\'><g transform=\'translate(50 50)\'><g transform=\'rotate(44.0329)\'><animateTransform attributeName=\'transform\' type=\'rotate\' values=\'0;45\' keyTimes=\'0;1\' dur=\'0.25s\' repeatCount=\'indefinite\'/><path d=\'M29.491524206117255 -5.5 L37.491524206117255 -5.5 L37.491524206117255 5.5 L29.491524206117255 5.5 A30 30 0 0 1 24.742744050198738 16.964569457146712 L24.742744050198738 16.964569457146712 L30.399598299691117 22.621423706639092 L22.621423706639096 30.399598299691114 L16.964569457146716 24.742744050198734 A30 30 0 0 1 5.5 29.491524206117255 L5.5 29.491524206117255 L5.5 37.491524206117255 L-5.499999999999997 37.491524206117255 L-5.499999999999997 29.491524206117255 A30 30 0 0 1 -16.964569457146705 24.742744050198738 L-16.964569457146705 24.742744050198738 L-22.621423706639085 30.399598299691117 L-30.399598299691117 22.621423706639092 L-24.742744050198738 16.964569457146712 A30 30 0 0 1 -29.491524206117255 5.500000000000009 L-29.491524206117255 5.500000000000009 L-37.491524206117255 5.50000000000001 L-37.491524206117255 -5.500000000000001 L-29.491524206117255 -5.500000000000002 A30 30 0 0 1 -24.742744050198738 -16.964569457146705 L-24.742744050198738 -16.964569457146705 L-30.399598299691117 -22.621423706639085 L-22.621423706639092 -30.399598299691117 L-16.964569457146712 -24.742744050198738 A30 30 0 0 1 -5.500000000000011 -29.491524206117255 L-5.500000000000011 -29.491524206117255 L-5.500000000000012 -37.491524206117255 L5.499999999999998 -37.491524206117255 L5.5 -29.491524206117255 A30 30 0 0 1 16.964569457146702 -24.74274405019874 L16.964569457146702 -24.74274405019874 L22.62142370663908 -30.39959829969112 L30.399598299691117 -22.6214237066391 L24.742744050198738 -16.964569457146716 A30 30 0 0 1 29.491524206117255 -5.500000000000013 M0 -20A20 20 0 1 0 0 20 A20 20 0 1 0 0 -20\' fill=\'%237c7c7c\'/></g></g></svg>',
  586. 'toggle': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M10.207,9.293l-0.707,0.707l5.657,5.657l1.414,-1.414l-4.242,-4.243l4.242,-4.243l-1.414,-1.414l-4.95,4.95Z\' /><path fill=\'%23222222\' d=\'M4.207,9.293l-0.707,0.707l5.657,5.657l1.414,-1.414l-4.242,-4.243l4.242,-4.243l-1.414,-1.414l-4.95,4.95Z\'/></svg>',
  587. };
  588.  
  589. function SVG_UI_File_Icon(icon_name) {
  590. switch(icon_name) {
  591. case 'favicon': return 'iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUmRcmZzP8zmf8pVcWPAAAAAXRSTlMAQObYZgAAAFBJREFUeF7tyqERwDAMBEE3mX5UiqDmqwwziTPHjG7xrmzrLFtRaApDIRiKQlMYCsFQFJrCUAiGotAU5hTA1WB4fhkMBsOJwWAwgHvB8CHpBcTbpxy4RZNvAAAAAElFTkSuQmCC';
  592. 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= ")';
  593. case 'file_icon_file_default': return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
  594. default: return 'url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons[icon_name] +'")';
  595. }
  596. }
  597. const SVG_UI_File_Icons = { // n.b.: order is important
  598. 'file_icon_dir': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M6,2.5l-1,-1.5l-5,0l0,12l14,0l0,-10.5l-8,0Z\' style=\'fill:%2339f;fill-rule:nonzero;\'/><rect x=\'1.5\' y=\'4\' width=\'11\' height=\'7.5\' style=\'fill:%239cf;\'/></svg>',
  599. 'file_icon_dir_open': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><path d=\'M6.1 2.7L4.8 1H0v12h14V2.7H6.1z\' fill=\'%2339f\' fill-rule=\'nonzero\'/><path d=\'M7 6h5.5v5.5h-11L7 6z\' fill=\'%239cf\'/></svg>',
  600. 'file_icon_file': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><g><path d=\'M8.3,0l-6.8,0l0,14l11,0l0,-9.8l-4.2,-4.2Z\' style=\'fill:%23888;fill-rule:nonzero;\'/><path d=\'M11,12.5l-8,0l0,-11l3.8,0l0,4.2l4.2,0l0,6.8Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/><path d=\'M8.3,4.2l1.9,0l-1.9,-2l0,2Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/></g></svg>',
  601. 'file_icon_invisible': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><g><path d=\'M8.3,0l-6.8,0l0,14l11,0l0,-9.8l-4.2,-4.2Z\' style=\'fill:%23888;fill-rule:nonzero;\'/><path d=\'M11,12.5l-8,0l0,-11l3.8,0l0,4.2l4.2,0l0,6.8Z\' style=\'fill:%23bbb;fill-rule:nonzero;\'/><path d=\'M8.3,4.2l1.9,0l-1.9,-2l0,2Z\' style=\'fill:%23bbb;fill-rule:nonzero;\'/></g><circle cx=\'7\' cy=\'9\' r=\'1.5\' style=\'fill:%23878787;\'/></svg>',
  602. 'file_icon_ignored': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M7,0c-3.9,0 -7,3.1 -7,7c0,3.9 3.1,7 7,7c3.9,0 7,-3.1 7,-7c0,-3.9 -3.1,-7 -7,-7Z\' style=\'fill:%23777;fill-rule:nonzero;\'/><path d=\'M7,2c2.8,0 5,2.2 5,5c0,2.8 -2.2,5 -5,5c-2.8,0 -5,-2.2 -5,-5c0,-2.8 2.2,-5 5,-5\' style=\'fill:%23ddd;fill-rule:nonzero;\'/><path d=\'M10.695,1.774l-8.839,8.839l1.626,1.626l8.839,-8.839l-1.626,-1.626Z\' style=\'fill:%23777;\'/></svg>',
  603. 'file_icon_dirinvisible': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M6,2.5l-1,-1.5l-5,0l0,12l14,0l0,-10.5l-8,0Z\' style=\'fill:%23888;fill-rule:nonzero;\'/><rect x=\'1.5\' y=\'4\' width=\'11\' height=\'7.5\' style=\'fill:%23bbb;\'/><circle cx=\'7\' cy=\'7.5\' r=\'1.5\' style=\'fill:%23888;\'/></svg>',
  604. 'file_icon_alias': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><path d=\'M3,12.5c0,-3.863 2.253,-7.5 6.259,-7.5\' style=\'fill:none;stroke:%23fc6;stroke-width:3px;\'/><path d=\'M13,5l-4,-4l0,8l4,-4Z\' style=\'fill:%23fc6;\'/></svg>',
  605. 'file_icon_archive': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M11,8.5l0,-1l2,0l0,2l-5,1l-2,0l0,1.5l4,0l0,1l-4,0l0,1l-3,0l0,-1l-2,0l0,-1l2,0l0,-1.5l-2,0l0,-2l2,0l0,-6.5l-2,0l0,-2l7,0l5,1l0,2l-2,0l0,-1l-5,0l0,6.5l5,0Z\' style=\'fill:%23777;\'/></svg>',
  606. 'file_icon_app': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path id=\'rect6894\' d=\'M6.125,0l-0.292,1.859c-0.587,0.135 -1.146,0.38 -1.64,0.693l0,-0.018l-1.532,-1.094l-1.221,1.221l1.094,1.532l0.018,0c-0.313,0.495 -0.559,1.051 -0.693,1.64l-1.859,0.292l0,1.75l1.859,0.292c0.134,0.589 0.38,1.145 0.693,1.64l-0.018,0l-1.094,1.532l1.221,1.221l1.532,-1.094l0,-0.018c0.494,0.313 1.053,0.558 1.64,0.693l0.292,1.859l1.75,0l0.292,-1.859c0.596,-0.137 1.14,-0.372 1.64,-0.693l1.532,1.112l1.221,-1.221l-1.112,-1.532c0.309,-0.492 0.523,-1.057 0.656,-1.64l1.896,-0.292l0,-1.75l-1.896,-0.292c-0.133,-0.583 -0.347,-1.148 -0.656,-1.64l0.018,0l1.094,-1.532l-1.221,-1.221l-1.532,1.094l0,0.018c-0.5,-0.321 -1.044,-0.556 -1.64,-0.693l-0.292,-1.859l-1.75,0Zm0.875,4.667c1.288,0 2.333,1.036 2.333,2.333c0,1.297 -1.045,2.333 -2.333,2.333c-1.288,0 -2.333,-1.036 -2.333,-2.333c0,-1.297 1.045,-2.333 2.333,-2.333Z\' style=\'fill:%237a7ab8;\'/></svg>',
  607. 'file_icon_audio': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><g id=\'Layer1\'><circle cx=\'7\' cy=\'7\' r=\'7\' style=\'fill:%230f8a8a;\'/></g><path d=\'M11,9.5l0,0c-0.019,0.681 -0.796,1.339 -1.75,1.475c-0.966,0.138 -1.75,-0.31 -1.75,-1c0,-0.69 0.784,-1.362 1.75,-1.5c0.268,-0.038 0.523,-0.031 0.75,0.013l0,-4.488l-4,0l0,6.5l0,0c-0.019,0.681 -0.796,1.339 -1.75,1.475c-0.966,0.138 -1.75,-0.31 -1.75,-1c0,-0.69 0.784,-1.362 1.75,-1.5c0.268,-0.038 0.523,-0.031 0.75,0.013l0,-6.488l6,-1l0,7.5Z\' style=\'fill:%23fff;\'/><path d=\'M11,2l-6,1l0,2l6,-1l0,-2Z\' style=\'fill:%23fff;\'/></svg>',
  608. 'file_icon_code': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,0l-14,0l0,14l14,0l0,-14Z\' style=\'fill:%2372d;fill-rule:nonzero;\'/><g><path d=\'M5.923,12.965c-1.049,0 -1.784,-0.161 -2.209,-0.48c-0.425,-0.317 -0.638,-0.82 -0.638,-1.503l0,-2.067c0,-0.446 -0.146,-0.764 -0.438,-0.95c-0.292,-0.188 -0.709,-0.281 -1.256,-0.281l0,-1.368c0.547,0 0.967,-0.094 1.259,-0.28c0.292,-0.186 0.438,-0.5 0.438,-0.938l0,-2.092c0,-0.675 0.217,-1.172 0.65,-1.491c0.432,-0.32 1.164,-0.479 2.195,-0.479l0,1.312c-0.401,0.01 -0.718,0.09 -0.952,0.24c-0.233,0.15 -0.348,0.426 -0.348,0.827l0,1.985c0,0.876 -0.511,1.396 -1.532,1.559l0,0.083c1.021,0.154 1.532,0.67 1.532,1.544l0,1.997c0,0.41 0.116,0.688 0.349,0.835c0.233,0.146 0.55,0.223 0.951,0.232l-0.001,1.315Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/><path d=\'M8.076,12.965l0,-1.313c0.392,-0.009 0.706,-0.089 0.944,-0.239c0.236,-0.15 0.355,-0.426 0.355,-0.829l0,-1.996c0,-0.867 0.511,-1.382 1.531,-1.545l0,-0.084c-1.02,-0.164 -1.53,-0.679 -1.53,-1.546l0,-1.997c0,-0.41 -0.116,-0.688 -0.349,-0.834c-0.232,-0.146 -0.549,-0.224 -0.951,-0.233l0,-1.313c1.049,0 1.785,0.159 2.21,0.479c0.423,0.319 0.637,0.821 0.637,1.505l0,2.065c0,0.447 0.146,0.765 0.438,0.951c0.292,0.187 0.711,0.28 1.257,0.28l0,1.367c-0.546,0.012 -0.967,0.107 -1.259,0.287c-0.293,0.183 -0.438,0.5 -0.438,0.945l0,2.08c0,0.674 -0.217,1.172 -0.65,1.491c-0.432,0.319 -1.165,0.479 -2.195,0.479Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/></g></svg>',
  609. 'file_icon_database': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,2.5l0,9c0,1.38 -3.137,2.5 -7,2.5c-3.863,0 -7,-1.12 -7,-2.5l0,-9\' style=\'fill:%23808080;\'/><path d=\'M13,2.5l0,9c0,0.828 -2.689,1.5 -6,1.5c-3.311,0 -6,-0.672 -6,-1.5l0,-9\' style=\'fill:%23b4b4b4;\'/><path d=\'M14,8.5c0,1.38 -3.137,2.5 -7,2.5c-3.863,0 -7,-1.12 -7,-2.5\' style=\'fill:%23808080;\'/><path d=\'M13,8.5c0,0.828 -2.689,1.5 -6,1.5c-3.311,0 -6,-0.672 -6,-1.5\' style=\'fill:%23b4b4b4;\'/><path d=\'M14,5.5c0,1.38 -3.137,2.5 -7,2.5c-3.863,0 -7,-1.12 -7,-2.5\' style=\'fill:%23808080;\'/><path d=\'M13,5.5c0,0.828 -2.689,1.5 -6,1.5c-3.311,0 -6,-0.672 -6,-1.5\' style=\'fill:%23b4b4b4;\'/><ellipse cx=\'7\' cy=\'2.5\' rx=\'7\' ry=\'2.5\' style=\'fill:%23808080;\'/><ellipse cx=\'7\' cy=\'2.5\' rx=\'5.5\' ry=\'1.5\' style=\'fill:%23b4b4b4;\'/></svg>',
  610. 'file_icon_ebook': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M2.668,-0.001c1.705,0.001 3.492,0.35 4.332,1.257c0.84,-0.908 2.627,-1.256 4.332,-1.257l2.668,0c0,4.112 0,8.43 0,12.541c-0.818,0 -2.181,0.005 -3,0.023c-1.184,0.026 -3.008,0.42 -3,1.437l-1,-0.017l-1,0.017c0.008,-1.017 -2,-1.437 -3,-1.437c-0.819,0 -2.182,-0.023 -3,-0.023l0,-12.541l2.668,0Z\' style=\'fill:%23808080;\'/><path d=\'M1.5,1.499l0,9.501l1.286,0c1.086,0.025 2.213,0.081 3.204,0.568l0.01,0.006c0,-2.859 0,-5.717 0,-8.576c0,-1.136 -1.49,-1.398 -2.336,-1.47c-0.708,-0.059 -1.438,-0.029 -2.164,-0.029Z\' style=\'fill:%23cdcdcd;\'/><path d=\'M12.5,1.499l0,9.501l-1.286,0c-1.086,0.025 -2.213,0.081 -3.204,0.568l-0.01,0.006c0,-2.859 0,-5.717 0,-8.576c0,-1.136 1.49,-1.398 2.336,-1.47c0.708,-0.059 1.438,-0.029 2.164,-0.029Z\' style=\'fill:%23cdcdcd;\'/></svg>',
  611. 'file_icon_font': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,0l-14,0l0,14l14,0l0,-14Z\' style=\'fill:%23709;fill-rule:nonzero;\'/><path d=\'M4.678,11.179l1.393,0l0,-8.266l-2.616,0l0,1.052l-1.455,0l0,-2.553l10,0l0,2.554l-1.456,0l0,-1.053l-2.599,0l0,8.266l1.347,0l0,1.409l-4.614,0l0,-1.409Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/></svg>',
  612. 'file_icon_graphics': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><path d=\'M7.774,8.285l4.726,4.715l-8,-3.525l-1.5,-4.975l-2,0l0,-3.5l3.525,0l-0.025,2l5,1.5l3.5,8l-4.7,-4.752c0.127,-0.22 0.2,-0.476 0.2,-0.748c0,-0.828 -0.672,-1.5 -1.5,-1.5c-0.828,0 -1.5,0.672 -1.5,1.5c0,0.828 0.672,1.5 1.5,1.5c0.283,0 0.548,-0.079 0.774,-0.215Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></svg>',
  613. 'file_icon_htm': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M6.967,0.5c-3.553,0.018 -6.467,2.947 -6.467,6.5c0,3.566 2.934,6.5 6.5,6.5c3.566,0 6.5,-2.934 6.5,-6.5c0,-3.553 -2.914,-6.482 -6.467,-6.5l-0.066,0Zm0.033,0l0,13m6.5,-6.5l-13,0m1.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.366c0,2.453 1.083,4.785 2.957,6.367m1,0c1.874,-1.582 2.957,-3.914 2.957,-6.367c0,-2.452 -1.083,-4.784 -2.957,-6.366\' style=\'fill:%23fff;fill-rule:nonzero;stroke:%23E44D26;stroke-width:1px;\'/></svg>',
  614. 'file_icon_ignoredimage': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M0.369,9.141c-0.252,-0.678 -0.369,-1.396 -0.369,-2.141c0,-3.863 3.137,-7 7,-7c3.863,0 7,3.137 7,7l-0.137,1.353l-3.853,-3.853l-3.5,3.5l-2.5,-2.5l-3.641,3.641Z\' style=\'fill:%23808080;\'/><path d=\'M0.839,10.151l-0.47,-1.01l3.641,-3.641l2.5,2.5l3.5,-3.5l3.853,3.853c-0.076,0.395 -0.201,0.778 -0.341,1.147l-10.371,3.345c-0.293,-0.194 -0.579,-0.416 -0.838,-0.651l-1.474,-2.043Z\' style=\'fill:%23fff;\'/><g><path d=\'M13.522,9.5c-0.99,2.64 -3.539,4.5 -6.522,4.5c-1.426,0 -2.753,-0.421 -3.849,-1.155l6.859,-6.866l3.512,3.521Z\' style=\'fill:%23808080;\'/><path d=\'M0.839,10.151l3.171,-3.172l1.761,1.761l-3.459,3.454c-0.591,-0.632 -1.079,-1.313 -1.473,-2.043Z\' style=\'fill:%23808080;\'/></g><circle cx=\'6\' cy=\'3.5\' r=\'1.5\' style=\'fill:%23fff;\'/></svg>',
  615. 'file_icon_image': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M0.369,9.141c-0.252,-0.678 -0.369,-1.396 -0.369,-2.141c0,-3.863 3.137,-7 7,-7c3.863,0 7,3.137 7,7l-0.137,1.353l-3.853,-3.853l-3.5,3.5l-2.5,-2.5l-3.641,3.641Z\' style=\'fill:%238080ff;\'/><path d=\'M0.839,10.151l-0.47,-1.01l3.641,-3.641l2.5,2.5l3.5,-3.5l3.853,3.853c-0.076,0.395 -0.201,0.778 -0.341,1.147l-10.371,3.345c-0.293,-0.194 -0.579,-0.416 -0.838,-0.651l-1.474,-2.043Z\' style=\'fill:%23fff;\'/><g><path d=\'M13.522,9.5c-0.99,2.64 -3.539,4.5 -6.522,4.5c-1.426,0 -2.753,-0.421 -3.849,-1.155l6.859,-6.866l3.512,3.521Z\' style=\'fill:%2333c;\'/><path d=\'M0.839,10.151l3.171,-3.172l1.761,1.761l-3.459,3.454c-0.591,-0.632 -1.079,-1.313 -1.473,-2.043Z\' style=\'fill:%2333c;\'/></g><circle cx=\'6\' cy=\'3.5\' r=\'1.5\' style=\'fill:%23fff;\'/></svg>',
  616. 'file_icon_markdown': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,0l-14,0l0,14l14,0l0,-14Z\' style=\'fill:%236a6a95;fill-rule:nonzero;\'/><path d=\'M12,11.5l-2.5,0l0,-5.143l-2.5,2.948l-2.5,-2.948l0,5.143l-2.5,0l0,-9l2.273,0l2.721,3.377l2.733,-3.377l2.273,0l0,9Z\' style=\'fill:%23DDD;fill-rule:nonzero;\'/></svg>',
  617. 'file_icon_office': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><rect x=\'10\' y=\'1.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'10\' y=\'4\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'10\' y=\'6.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'10\' y=\'9\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'10\' y=\'11.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'1.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'4\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'6.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'9\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'11.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'1.5\' y=\'1.5\' width=\'4\' height=\'11\' style=\'fill:%23cdcdcd;\'/></svg>',
  618. 'file_icon_pdf': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23d20000;fill-rule:nonzero;\'/><path d=\'M12.69,9.115c-0.075,0.048 -0.291,0.076 -0.428,0.076c-0.443,0 -0.99,-0.204 -1.762,-0.534c0.297,-0.022 0.568,-0.031 0.811,-0.031c0.446,0 0.575,0 1.012,0.109c0.433,0.108 0.438,0.333 0.367,0.38Zm-7.72,0.069c0.172,-0.303 0.349,-0.622 0.526,-0.961c0.44,-0.83 0.719,-1.484 0.923,-2.017c0.413,0.749 0.926,1.383 1.525,1.894c0.077,0.063 0.157,0.125 0.242,0.189c-1.224,0.246 -2.283,0.539 -3.216,0.895Zm1.428,-7.856c0.244,0 0.384,0.612 0.395,1.191c0.011,0.573 -0.121,0.974 -0.29,1.277c-0.141,-0.445 -0.205,-1.14 -0.205,-1.596c0,-0.001 -0.01,-0.872 0.1,-0.872Zm-4.788,11.025c0.142,-0.378 0.687,-1.124 1.494,-1.788c0.051,-0.038 0.177,-0.157 0.292,-0.266c-0.843,1.35 -1.412,1.885 -1.786,2.054Zm11.312,-4.029c-0.242,-0.241 -0.789,-0.367 -1.615,-0.377c-0.56,-0.008 -1.23,0.041 -1.942,0.139c-0.315,-0.184 -0.641,-0.381 -0.9,-0.622c-0.689,-0.646 -1.262,-1.539 -1.621,-2.521c0.021,-0.095 0.044,-0.173 0.062,-0.256c0,0 0.387,-2.208 0.283,-2.954c-0.015,-0.105 -0.021,-0.132 -0.051,-0.212l-0.033,-0.089c-0.104,-0.243 -0.313,-0.502 -0.639,-0.488l-0.19,-0.006l-0.003,0c-0.362,0 -0.661,0.186 -0.736,0.461c-0.236,0.872 0.007,2.171 0.448,3.856l-0.114,0.275c-0.315,0.768 -0.711,1.542 -1.058,2.225l-0.048,0.09c-0.365,0.717 -0.7,1.328 -1,1.843l-0.313,0.167c-0.021,0.014 -0.556,0.294 -0.681,0.37c-1.064,0.634 -1.77,1.356 -1.887,1.929c-0.037,0.181 -0.009,0.414 0.18,0.525l0.302,0.15c0.13,0.064 0.272,0.097 0.41,0.097c0.757,0 1.637,-0.941 2.845,-3.053c1.4,-0.457 2.994,-0.836 4.39,-1.045c1.062,0.6 2.369,1.015 3.194,1.015c0.147,0 0.274,-0.013 0.377,-0.042c0.156,-0.04 0.29,-0.13 0.372,-0.256c0.158,-0.238 0.193,-0.569 0.148,-0.91c-0.01,-0.1 -0.093,-0.226 -0.18,-0.311Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/></svg>',
  619. 'file_icon_playlist': '<svg viewBox=\'0 0 14 14\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><path fill=\'%230f8a8a\' fill-rule=\'nonzero\' d=\'M0 0h14v14H0z\'/><path fill=\'%23fff\' d=\'M1.5 1.5h8v1h-8zM1.5 4h8v1h-8zM1.5 6.5h8v1h-8zM1.5 9h7v1h-7zM1.5 11.5H7v1H1.5zM11 1.5h1v10c-.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 01.75.013V1.5z\'/></svg>',
  620. 'file_icon_text': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,0l-14,0l0,14l14,0l0,-14Z\' style=\'fill:%236A6A95;fill-rule:nonzero;\'/><rect x=\'6.5\' y=\'1.5\' width=\'6\' height=\'1\' style=\'fill:%23fff;\'/><rect x=\'1.5\' y=\'1.5\' width=\'3.5\' height=\'3.5\' style=\'fill:%23fff;\'/><rect x=\'1.5\' y=\'6.5\' width=\'11\' height=\'1\' style=\'fill:%23fff;\'/><rect x=\'6.5\' y=\'4\' width=\'6\' height=\'1\' style=\'fill:%23fff;\'/><rect x=\'1.5\' y=\'11.5\' width=\'8\' height=\'1\' style=\'fill:%23fff;\'/><rect x=\'1.5\' y=\'9\' width=\'11\' height=\'1\' style=\'fill:%23fff;\'/></svg>',
  621. 'file_icon_video': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><g id=\'Layer1\'><path d=\'M14,14l0,-14l-14,0l0,14l14,0Z\'/><path d=\'M9.5,3l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M3.5,3l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M6.5,3l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M12.5,3l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M9.5,13l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M3.5,13l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M6.5,13l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M12.5,13l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M12.5,10l0,-6l-11,0l0,6l11,0Z\' style=\'fill:%23eda412;\'/></g></svg>',
  622. // the following are the same:
  623. 'file_icon_bin': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><g><path d=\'M1.247,6.495l3.263,0l0,-1.067l-0.881,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.052,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M7,6.588c1.082,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.743,-2.521 -1.825,-2.521c-1.082,0 -1.825,0.843 -1.825,2.521c0,1.677 0.743,2.567 1.825,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M9.598,6.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.283,0.479l0,0.82l0.927,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M2.825,12.588c1.082,0 1.824,-0.89 1.824,-2.567c0,-1.67 -0.742,-2.521 -1.824,-2.521c-1.083,0 -1.825,0.843 -1.825,2.521c0,1.677 0.742,2.567 1.825,2.567Zm0,-1.021c-0.31,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.262,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M5.423,12.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M11.175,12.588c1.083,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.742,-2.521 -1.825,-2.521c-1.082,0 -1.824,0.843 -1.824,2.521c0,1.677 0.742,2.567 1.824,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.31,0 0.572,0.201 0.572,1.5c0,1.299 -0.262,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></g></svg>',
  624. 'file_icon_other': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><g><path d=\'M1.247,6.495l3.263,0l0,-1.067l-0.881,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.052,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M7,6.588c1.082,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.743,-2.521 -1.825,-2.521c-1.082,0 -1.825,0.843 -1.825,2.521c0,1.677 0.743,2.567 1.825,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M9.598,6.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.283,0.479l0,0.82l0.927,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M2.825,12.588c1.082,0 1.824,-0.89 1.824,-2.567c0,-1.67 -0.742,-2.521 -1.824,-2.521c-1.083,0 -1.825,0.843 -1.825,2.521c0,1.677 0.742,2.567 1.825,2.567Zm0,-1.021c-0.31,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.262,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M5.423,12.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M11.175,12.588c1.083,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.742,-2.521 -1.825,-2.521c-1.082,0 -1.824,0.843 -1.824,2.521c0,1.677 0.742,2.567 1.824,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.31,0 0.572,0.201 0.572,1.5c0,1.299 -0.262,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></g></svg>',
  625. 'file_icon_system': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><g><path d=\'M1.247,6.495l3.263,0l0,-1.067l-0.881,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.052,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M7,6.588c1.082,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.743,-2.521 -1.825,-2.521c-1.082,0 -1.825,0.843 -1.825,2.521c0,1.677 0.743,2.567 1.825,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M9.598,6.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.283,0.479l0,0.82l0.927,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M2.825,12.588c1.082,0 1.824,-0.89 1.824,-2.567c0,-1.67 -0.742,-2.521 -1.824,-2.521c-1.083,0 -1.825,0.843 -1.825,2.521c0,1.677 0.742,2.567 1.825,2.567Zm0,-1.021c-0.31,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.262,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M5.423,12.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M11.175,12.588c1.083,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.742,-2.521 -1.825,-2.521c-1.082,0 -1.824,0.843 -1.824,2.521c0,1.677 0.742,2.567 1.824,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.31,0 0.572,0.201 0.572,1.5c0,1.299 -0.262,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></g></svg>'
  626. };
  627. // Programatically add File icon CSS rules
  628. function CSS_UI_Icon_Rules() {
  629. let rules = '', kind, class_name;
  630. rules = `
  631. #menu ul a::before { background-image:${ SVG_UI_File_Icon('file_icon_file') }; }
  632. #menu ul a[href^="file"]::before { background-image:${ SVG_UI_File_Icon('file_icon_dir') }; }
  633. #menu ul a[href^="http"]::before { background-image:${ SVG_UI_File_Icon('file_icon_htm') }; }
  634. body:not(.use_custom_icons) #dir_list tr.dir a.icon span::before { background-image:${ SVG_UI_File_Icon('file_icon_dir_default') }; background-size:auto 13px; }
  635. body:not(.use_custom_icons) #dir_list tr.file:not(.app) a.icon span::before { background-image:${ SVG_UI_File_Icon('file_icon_file_default') }; background-size:auto 13px; }`;
  636. for ( let icon in SVG_UI_File_Icons ) {
  637. kind = icon.slice(icon.lastIndexOf('_') + 1);
  638. class_name = kind;
  639. // exceptions
  640. if ( kind === 'dirinvisible' ) { class_name = 'dir.invisible'; }
  641. if ( kind === 'ignoredimage' ) { class_name = 'ignored_image'; }
  642. if ( kind === 'open' ) { class_name = 'has_subdirectory'; kind = 'dir_open'; }
  643. if ( /alias|symlink/.test(kind) ) { class_name = 'link'; }
  644. // add rules for dir_list items, content_header, stats details:
  645. rules += `body.use_custom_icons #dir_list tr.${ class_name } a.icon span.has_icon_before_before, #content_pane[data-content="has_${ class_name }"] #title span::before, body.use_custom_icons #stats tbody tr.${ class_name } .stats_kind.has_icon_before::before { background-image: url("data:image/svg+xml;utf8,${ SVG_UI_File_Icons['file_icon_'+kind] }"); }`;
  646. }
  647. return rules;
  648. }
  649. // Text Editing UI Icons
  650. function SVG_Text_Editing_UI_Icon(icon_name) { return `url("data:image/svg+xml;utf8,${ SVG_Text_Editing_UI_Icons[icon_name] }")`; }
  651. const SVG_Text_Editing_UI_Icons = {
  652. 'toggle_theme': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><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 6V2z\' fill=\'%23333\'/></svg>',
  653. 'show_markdown': '<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'100\' height=\'60\'><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>',
  654. 'show_source': '<svg viewBox=\'0 0 22 14\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><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>',
  655. 'show_preview': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><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>',
  656. 'show_html': '<svg viewBox=\'0 0 22 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><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>',
  657. 'toggle_split': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><path d=\'M0 0v16h16V0H0zm14 14H9V2h5v12zm-7 0H2V2h5v12z\' fill=\'%23333\' fill-rule=\'nonzero\'/></svg>',
  658. 'save_btn': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><g fill=\'%23333\'><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>',
  659. 'save_btn_edited': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><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>'
  660. };
  661. //***** END UI ELEMENTS
  662.  
  663. //***** STYLES *****//
  664. const $warning_styles = `
  665. #warnings_container { width:26em; transform:translate(-50%, 0); display:none; flex-direction:column; border-radius:0 0 3px 3px; position:absolute; top:0; left:50%; z-index:9999; box-shadow:0px 2px 12px 0 #333; font-size:0.875em; color:#111; overflow:hidden; }
  666. body.has_warning #warnings_container { display:flex; }
  667. #warnings_header { padding:1rem 1.5rem; background-position:left 1.25rem center; background-repeat:no-repeat; background-size:24px; }
  668. #warnings_container:not(.warning_make_playlist) #warnings_header { background-image:${ SVG_UI_Icon("error") }; }
  669. #warnings_header h3 { display:none; margin:0; text-indent:2.25em; }
  670. #warnings_container:not(.warning_make_playlist) h3#warning_header, #warnings_container.warning_make_playlist h3#make_playlist_header { display:block;}
  671. body#top.edited #warnings_container.unloading h3::before { content: "Text Editor: " }
  672. #warnings .warning { padding: 0 1.5rem 1rem; display:none; hyphens:none; }
  673. #warning_buttons_container { padding:1rem 1.5rem; }
  674. #warning_buttons { display:flex; flex-direction:row; }
  675. #warning_buttons button { min-width:4em; display:none; font-size:1em; }
  676. button.focus, button:focus { background-color: #0E4399; color: #EEE; outline:none; }
  677. #warning_btn_dont_save { margin-right: auto; }
  678. #warning_btn_cancel, #warning_btn_clear, #warning_btn_save { margin-left: 0.5rem; }
  679. #warning_btn_ok { margin-left:auto; }
  680. #warnings_container.warning_close_font #warning_close_font, #warnings_container.warning_close_playlist #warning_close_playlist, #warnings_container.unloading #warning_unsaved_text, #warnings_container.unloading #warning_btn_dont_save, #warnings_container.unloading #warning_btn_cancel, #warnings_container.unloading #warning_btn_save { display:inline-block; }
  681. #warnings_container.clear #warning_buttons { justify-content:space-between; }
  682. #warnings_container.clear #warning_clear_text, #warnings_container.clear #warning_btn_cancel, #warnings_container.clear #warning_btn_clear { display:inline-block; }
  683. #warnings_container.warning_local_bookmark #warning_local_bookmark, #warnings_container.warning_local_bookmark #warning_btn_ok { display:inline-block; margin-left:auto; }
  684. #warnings_container.warning_local_file #warning_local_file, #warnings_container.warning_local_file #warning_btn_ok { display:inline-block; margin-left:auto; }
  685. #warnings_container.warning_close_font #warning_btn_ok { display:inline-block; margin-left:auto; }
  686. #warnings_container.warning_close_font #warning_btn_cancel { display:inline-block; }
  687. #warnings_container.warning_close_playlist #warning_btn_ok { display:inline-block; margin-left:auto; }
  688. #warnings_container.warning_close_playlist #warning_btn_cancel { display:inline-block; }
  689. #warning_make_playlist fieldset { margin:0; padding:0; border:0; }
  690. #warning_make_playlist fieldset div { padding:0 0 2px; }
  691. #warning_make_playlist .indent { text-indent:2em; }
  692. #warning_make_playlist input { margin-right:6px; }
  693. #warnings_container.warning_make_playlist #warning_make_playlist { display:flex; flex-direction:column; }
  694. #warnings_container.warning_make_playlist #warning_btn_ok, #warnings_container.warning_make_playlist #warning_btn_cancel { display: inline-block; }
  695. #warnings_container.warning_no_playlist #warning_no_playlist, #warnings_container.warning_no_playlist #warning_btn_ok { display: inline-block; }
  696. #warnings_container.warning_local_playlist #warning_local_playlist, #warnings_container.warning_local_playlist #warning_btn_ok { display:inline-block; margin-left:auto; }
  697. `;
  698. var $main_styles = `
  699. :root, html, body { margin:0; padding:0; width:100%; max-width:100%; height:100vh; overflow:hidden; border:0; border-radius:0; font-family: ${$settings.UI_font}; font-size: ${ $settings.UI_font_size}; hyphens:auto; display:flex; }
  700. table { width:100%; border:0; border-collapse: collapse; }
  701. #sidebar_wrapper li, #toolbar li { list-style:none; }
  702. a, a:hover { text-decoration: none !important; }
  703. button { padding:2px 6px; border:solid 1px #333; border-radius:3px; cursor:pointer; height:18px; font-family:${$settings.UI_font} !important; font-size:1em !important; line-height:0;}
  704. button.focus, button:focus { outline:none; border-radius:3px !important; border-style:solid !important; border-width:1px !important; border-color:#222 !important; }
  705. textarea:focus, audio:focus { outline:none; }
  706. #main_content { width:100%; display:flex; flex-direction:row; overflow:hidden; }
  707. /***** SIDEBAR STYLES *****/
  708. #sidebar_wrapper { min-width:220px; padding:0; position:relative; z-index:1; display:flex; flex-direction:column; }
  709. #sidebar { overflow:hidden; font-size:0.875rem; display:flex; flex-direction:column; flex-basis:100%; }
  710. #sidebar_header { font-size:0.875rem; position:relative; z-index:3; user-select:none; -webkit-user-select:none; display:flex; flex-direction:column; }
  711. #sidebar_title { font-weight:normal; display:flex; flex-direction:row; }
  712. #sidebar_title div { padding: 4px 6px; text-align:center; letter-spacing:0.5em; text-indent:0.75em; flex-basis:100%; }
  713. #sidebar_header_body { display:flex; flex-direction:column; }
  714. #hide_sidebar { position:absolute; top:0; right:0; cursor:pointer; width:24px; height:21px; z-index:9997; background-image: ${ SVG_UI_Icon("toggle") }; background-size:18px; background-position:center; }
  715. #sidebar ul { -webkit-margin-before:0em !important; -webkit-margin-after:0em !important; -webkit-padding-start:0em; }
  716. #sidebar_menus { cursor:pointer; display:flex; flex-direction:row; }
  717. /* PARENT MENU */
  718. #parent_dir_menu { flex-basis:24px; max-width:24px; min-width:24px; padding:0; position:relative; }
  719. #parent_dir_nav { margin:0; padding:0; display:block; position:absolute; top:0; right:0; bottom:0; left:0; }
  720. #parent_dir_nav a { height:100%; padding:0; text-align:center; background-position:center; background-repeat:no-repeat; background-size:12px; }
  721. /* PARENTS MENU */
  722. #parents_dir_menu { padding:0; flex-grow:1; }
  723. #parents_dir_nav { margin:0; padding:0; display:flex; line-height:1.4; }
  724. #current_dir_path { cursor:pointer; flex:auto; font-weight:bold; hyphens:none; padding:4px 6px 4px 6px; text-align:center; word-break:break-word; z-index:9998; }
  725. #close_playlist_btn_container { display:none; flex-direction:column; justify-content:center; flex-basis:24px; max-width:24px; min-width:24px; padding:0; }
  726. #close_playlist_btn { background-image:${ SVG_UI_Icon("multiply") }; background-position:center; background-size:12px; display:flex; flex-direction:column; justify-content:center; flex-basis:24px; max-width:24px; min-width:24px; padding:0; opacity:0.55; }
  727. #parents_links { margin:0; padding:0; display:none; position:absolute; right:0; left:0; box-shadow: 0px 4px 6px -3px #333; z-index:9998; }
  728. #parents_links a { margin:0; padding:4px 8px; display:block; }
  729. /* MAIN MENU */
  730. #menu_container { display:flex; flex-direction:column; justify-content:center; flex-basis:24px; max-width:24px; min-width:24px; padding:0; }
  731. #menu_nav { margin:0; padding:0; display:block; cursor:pointer; }
  732. #menu_nav div { width:24px; background-image: ${ SVG_UI_Icon("menu")}; background-position:center; background-repeat:no-repeat; background-size:13px; }
  733. #menu { display:none; margin:0; padding:0; position:absolute; right:0;left:0; zindex:9998; box-shadow: 0px 4px 6px -3px #333; z-index:9998; }
  734. #menu li.has_submenu { position:relative; background-position:right 6px center; background-repeat:no-repeat; background-size:12px; }
  735. #menu li.bookmark a::before { content:""; width:24px; height: 12px; background-size:12px; }
  736. .submenu { display:none; margin:0; padding:0; position:absolute; top:-1px !important; left:100%; right:0; width:100%; box-shadow:0px 4px 6px -3px #333; width:100%; max-width:240px; }
  737. #menu ul.submenu li a { margin:0; padding:6px 8px 6px 0; }
  738. #menu input { width:0; float:left; }
  739. .menu_item { margin:0; padding:6px 8px 6px 0; display:flex; }
  740. .menu_item::before { content:""; width:24px; height:9px; margin:2px 0 -2px; display:inline-block; background-position:center; background-repeat:no-repeat; }
  741. li.has_submenu:hover > span, .menu_item:hover, #sidebar_menus li.selected > span { font-weight:bold; }
  742. /* SIDEBAR BUTTONS */
  743. #sidebar_buttons { position:relative; display:flex; flex-direction:row; }
  744. #sidebar_buttons_left { padding:6px; }
  745. #show_details { margin-top:0; margin-right:0.5em; padding:0 4px 0 4px; }
  746. #show::before { content:"Show "; }
  747. #show_invisibles_container { cursor:pointer; display:inline-flex; vertical-align:text-top; padding:0; height:100%; }
  748. #show_invisibles { cursor:pointer; margin:0 4px 0 0; }
  749. #show_invisibles:hover span { font-weight:bold; }
  750. /* GRID BTN */
  751. #grid_btn { display:none; margin:0 0 0 auto; padding:0; width:24px; position:relative; z-index:9997; background-color:inherit; background-position:center; background-size:14px; cursor:pointer; outline:none; }
  752. #grid_btn ul { display:none; margin:0; padding:0; padding-right:24px; padding-left:0px; position:absolute; top:-1px; right:-1px; background-position:right 5px top 8px; background-repeat:no-repeat; background-size:14px; box-shadow:0px 4px 6px -3px #333; }
  753. #grid_btn ul li { padding:4px 6px; text-align:right; width:100%; white-space:pre; box-sizing:border-box; }
  754. /* SORTING ITEMS */
  755. #sorting_row_1 { display:flex; cursor:pointer; justify-content:space-between; flex-direction:row; }
  756. #sorting_row_2 { display:none; cursor:pointer; justify-content:space-between; flex-direction:row; }
  757. #sorting_row_1 div, #sorting_row_2 div { flex-grow:1; padding:0; }
  758. #sorting .sorting { white-space:pre; }
  759. #sorting_row_1 span { display:inline-block; padding:6px 0; }
  760. #sorting_row_2 span { display:inline-block; padding:0 0 6px 0; }
  761. .sorting:hover span { font-weight:bold; }
  762. .sorting span::before, .sorting span::after { content:""; width:16px; height:8px; display:inline-block; position:relative; color:#CCC; background-position:center; background-repeat:no-repeat; background-size:10px; }
  763. .sorting.down span::after { transform:rotate(180deg) }
  764. #sort_by_name input { display:none; margin:0 4px 0 2px; }
  765. #sort_by_name, #sort_by_size { text-align:left; }
  766. #sort_by_date, #sort_by_kind { text-align:center; }
  767. #sort_by_default, #sort_by_time, #sort_by_ext { text-align:right; }
  768. body.sort_direction_up .sorting span::after { transform:rotate(180deg) !important; }
  769. #sort_by_time { display:none; }
  770. body.has_media #sort_by_name span { padding:4px 0; }
  771. body.has_media #sort_by_name input { position:relative; bottom:-2px; }
  772. body.has_media #sort_by_default { text-align:center; }
  773. body.has_media #sort_by_time, body.has_playlist #sort_by_time { display:block; }
  774. /* TEXT EDITOR ROW */
  775. #text_editor_row { display:none; }
  776. #text_editor_row a { padding:6px 0 6px 16px; text-align:left; font-weight:bold; }
  777. /***** DIR_LIST STYLES *****/
  778. #dir_list_wrapper { overflow-x:hidden; overflow-y:auto; position:relative; margin-bottom:-1px; }
  779. #dir_list { overflow:hidden; position:relative; font-size:0.875rem; }
  780. #tbody { counter-reset:row; height:100%; overflow:hidden; transition:opacity .125s; background-position:center; background-repeat:no-repeat; background-size:50%; }
  781. #tbody > tr { margin-inline-start:0; display:none; grid-gap:0; grid-template-columns:minmax(auto,6rem) minmax(12em,1fr) minmax(auto,6em); }
  782. .tbody_row_cell { padding:0; }
  783. .tbody_row_cell_name { grid-column: 1 / span 3; font-variant-numeric:tabular-nums; }
  784. .tbody_row_cell_name_a { display:flex; text-decoration:none; -webkit-padding-start:0; -moz-padding-start:0; }
  785. .tbody_row_cell_name_a::before { display:none; counter-increment:row; content:counter(row); width:36px; min-width:36px; height:14px; max-height:14px; min-height:14px; text-align:right; padding-right:1px; }
  786. .tbody_row_cell_name_a_span { display:flex; line-height:1.4; text-align:left; word-break:break-word; }
  787. .tbody_row_cell_name_a_span input { display:none; margin:2px 6px 0 0; }
  788. .tbody_row_cell_details { display:none; text-align:right; white-space:nowrap; max-height:1em; font-variant-numeric:tabular-nums; padding:0 12px 3px 0; }
  789. .tbody_row_cell_details.size { grid-column: 1; grid-row: 2; padding:0 8px 3px 8px; }
  790. .tbody_row_cell_details.date { grid-column: 2; grid-row: 2; text-align:right; overflow:hidden; overflow-wrap:break-word; text-overflow:""; padding-right:8px; }
  791. .tbody_row_cell_details.date span { white-space:pre; display:block; float:right; }
  792. .tbody_row_cell_details.kind { grid-column: 3; grid-row: 2; overflow:hidden; text-overflow:ellipsis; }
  793. .tbody_row_cell_details.kind::first-letter { text-transform:uppercase; }
  794. .tbody_row_cell_name_a { padding:5px 12px 5px 0; }
  795. .tbody_row_cell_name_a::before { margin-top:1px; }
  796. .tbody_row_cell_media_duration { display:none; grid-column: 3 / span 1; font-variant-numeric:tabular-nums; padding:6px 12px 3px 6px; text-align:right !important; }
  797. .has_subdirectory .dir_list_subdir { display:block; grid-column:1/ span 3; grid-row:3; width:100%; border:0; }
  798. tr.media:not(.local) .tbody_row_cell_name { grid-column: 1 / span 2; }
  799. tr.media:not(.local) .tbody_row_cell_name_a { padding-right:0; }
  800. tr.media:not(.local) .tbody_row_cell_media_duration { display:inline-block; }
  801. tr.media.local .has_icon_before_before { opacity:0.5; }
  802. #tbody tr:hover a, tr.selected a, tr.media[class*="_loaded"] a { font-weight:bold; opacity:1 !important; }
  803. tr.disabled, body.ignore_ignored_items tr.ignored, tr.media.local input { cursor:not-allowed; opacity:0.66; }
  804. #dir_list #tbody .error { display:block; padding:6px 8px; }
  805. #dir_list #tbody .error::before { display:none; }
  806. /***** SIDEBAR FOOTER *****/
  807. #tfoot { margin-top:auto; padding:0; position:relative; font-size:0.875rem; user-select:none; -webkit-user-select:none; display:flex; flex-direction:column; }
  808. /* STATS */
  809. #stats_container { display:block; max-height: ${ (window.innerHeight * 0.33) }px; }
  810. #stats { cursor:pointer; float:left; width:100%; overflow:hidden;font-size:0.875rem; }
  811. body:not(.has_stats) #stats tbody, body.has_stats #stats_summary, body:not(.has_stats) #stats_summary_detailed_container, #stats .stats_kind span.file, #stats .stats_kind span.media, #total_duration { display:none; }
  812. body.theme_light #footer_links:hover ul, body.theme_light #tfoot:hover, body.theme_light #stats_summary_detailed_container { box-shadow: 0px -4px 4px 0px rgba(128,128,128,0.6); }
  813. body.theme_dark #footer_links:hover ul, body.theme_dark #tfoot:hover, body.theme_dark #stats_summary_detailed_container { box-shadow: 0px -4px 4px 0px rgba(32,32,32,0.6); }
  814. #stats_summary_detailed_container { overflow-y:scroll; }
  815. #stats_summary_detailed_dirs .stats_kind::before { background-image:${ SVG_UI_File_Icon("file_icon_dir") }; }
  816. #stats_summary_detailed_files .stats_kind::before { background-image:${ SVG_UI_File_Icon("file_icon_file_default") }; }
  817. #stats_details_container { display:block; position:relative; overflow-y:scroll; }
  818. #stats_details { display:block; font-size:0.875rem; }
  819. #stats_summary tr { padding:5px 8px; }
  820. #stats_summary_totals, body.has_media #total_duration { display:inline-block; text-align:left; font-weight:normal; white-space:normal; padding-right:1em; }
  821. body.has_media #total_duration::before { content:"Total Time: "; }
  822. #stats_summary_detailed_container tr:hover, #stats_details tr:hover { font-weight:bold; }
  823. #stats tr { line-height:1.2; }
  824. #stats a { padding:4px 12px 4px 0; }
  825. #stats_summary_detailed_total th, #stats_summary_playlist_container { padding:5px 8px; overflow:hidden; white-space:pre; }
  826. #stats a::before { content:attr(data-count); }
  827. #stats .stats_kind span { display:inline-block; margin-right:0.5em; white-space:pre-wrap; }
  828. #stats .stats_kind span::first-letter { text-transform:uppercase }
  829. /* SIDEBAR FOOTER LINKS */
  830. #footer_links { margin-top:-1px; position:absolute; z-index:1; right:0; cursor:pointer; float:right; width:24px; height:100%; background-image:${ SVG_UI_Icon("toggle") }; transform:rotate(180deg) !important; background-size:18px; background-position:2px center; }
  831. #footer_links ul { display:none; margin:0; padding:0; position:absolute; top:calc(100% - 1px); right:unset; left:calc(100% - 27px); white-space:nowrap; box-shadow:-0px -3px 6px -3px #333; transform:rotate(180deg) !important; }
  832. #open_in_content_pane { padding:4px 6px; text-align:right; }
  833. #view_directory_source { padding:4px 6px; text-align:right; }
  834. /* CLASSES AND ELEMENTS */
  835. .has_icon_before::before, .has_icon_before_before { content:""; display:block; float:left; max-width:28px; width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; margin-top:1px; background-position:center; background-repeat:no-repeat; background-size:14px; }
  836. tr.ignored .has_icon_before::before, tr.ignored .has_icon_before_before { filter:grayscale(100%); }
  837. .has_checkmark_before::before { content:""; height:9px; background-position:center; background-repeat:no-repeat; }
  838. ul.has_popout_menu, .editor_theme_light #toolbar ul.has_popout_menu { background-color:#C0C0C0; border:solid 1px #666; }
  839. ul.has_popout_menu li { background-color:#D0D0D0; }
  840. ul.has_popout_menu li:hover { background-color:#E0E0E0; }
  841. .theme_dark #sidebar_wrapper ul.has_popout_menu { border:solid 1px #111; }
  842. .theme_dark #sidebar_wrapper ul.has_popout_menu, .theme_dark #sidebar_wrapper ul.has_popout_menu li { background-color:#505050; }
  843. body.editor_theme_dark #toolbar ul.has_popout_menu li { background-color:#C0C0C0; }
  844. .theme_dark #sidebar_wrapper ul.has_popout_menu li:hover { background-color:#686868; }
  845. /***** END SIDEBAR STYLES *****/
  846. /***** CONTENT STYLES *****/
  847. #content_pane { height:100%; padding:0; position:relative; transform:scale(1); vertical-align:top; display:flex; flex-direction:column; contain:strict; }
  848. #content_header { display:block; font-size:0.875rem; position:relative; z-index:3; }
  849. #content_header table { font-size:0.875rem; z-index:2; }
  850. #content_title { text-align:center; display:flex; flex-direction:row; justify-content:space-between; }
  851. /* CONTENT TITLE BUTTONS LEFT */
  852. #title_buttons_left, #title_buttons_right { display:flex; padding:4px 6px; text-align:left; width:auto; white-space:nowrap; }
  853. #reload_btn { width:52px; }
  854. #reload_btn::before { content:"Reload"; }
  855. #prev_next_btns { margin-left:4px; cursor:pointer; line-height:1; padding:0; position:relative; }
  856. #prev_next_btns span { width:2em; height:18px; }
  857. #prev_btn, #next_btn { background-position:center 36%; background-repeat:no-repeat; background-size:33%; }
  858. #prev_btn { background-image:${ SVG_UI_Icon("chevron_left") }; }
  859. #next_btn { background-image:${ SVG_UI_Icon("chevron_right") }; }
  860. /***** CONTENT TITLE *****/
  861. #title { line-height:1.4; min-width:10em; min-height:18px; padding:4px 8px; text-align:center; word-break:break-word; vertical-align:top; cursor:pointer; }
  862. #title span { font-weight:bold; hyphens:none; }
  863. #title span::before { content:""; margin-top:1px; margin-bottom:-2px; width:24px; height:14px; display:inline-block; background-position:center; background-repeat:no-repeat; font-weight:normal; }
  864. #title span::after { font-weight:bold; }
  865. #content_pane[data-content="has_image"] #title span::after { font-weight:normal; }
  866. /* CONTENT TITLE BUTTONS RIGHT */
  867. #title_buttons_right { text-align:right; }
  868. #scale { cursor:pointer; line-height:1; margin-right:4px; padding:0 4px; position:relative; background-color:#FFF; }
  869. #scale span { width:2em; height:18px; background-position:center 36%; background-repeat:no-repeat; background-size:10px; }
  870. #decrease { margin-left:-4px; background-image:${ SVG_UI_Icon("minus") }; }
  871. #increase { margin-right:-4px; background-image:${ SVG_UI_Icon("plus") }; }
  872. #close_btn { padding:0px; width:52px; }
  873. #close_btn::before { content:"Close"; }
  874. .split_btn { display:none; }
  875. .split_btn span { display:inline-flex; }
  876. .split_btn::after { content:""; position: absolute; top: 0; bottom: 0; left: 50%; }
  877. #open_in_text_editor { display:none; margin-right:4px; }
  878. /* CONTENT AUDIO TITLE */
  879. #content_audio_title { display:none;}
  880. #content_audio_title span { display:block; width:100%; cursor:pointer; padding:4px 6px 6px; font-weight:bold; text-align:center; line-height:1.4; }
  881. #content_audio_title span::before { content:""; padding-right:18px; font-weight:normal; background-position:center; background-position:right 4px center; background-repeat:no-repeat; }
  882. /* CONTENT AUDIO PLAYER */
  883. #content_audio { display:none; justify-content:center; padding-bottom:6px; position:relative; }
  884. #content_audio > div > div { text-align:center; font-weight:bold;}
  885. #audio_container { padding:0; height:32px; display:flex; background-color:rgb(241, 243, 244); flex-direction:row; }
  886. #prev_track, #next_track { width:2rem; padding:0; display:inline-block; overflow:auto; background-image:${ SVG_UI_Icon("prev_next_track") }; background-position:center; background-repeat:no-repeat; }
  887. #prev_track { transform:rotate(180deg) !important; }
  888. #audio { height:32px; }
  889. audio::-webkit-media-controls-panel { padding:0; }
  890. audio::-webkit-media-controls-enclosure { border-radius:0; }
  891. #close_audio { width:32px; padding:0; position:relative; display:inline-block; background-image:${ SVG_UI_Icon("multiply") }; background-position:center; background-repeat:no-repeat; background-size:14px; }
  892. #audio_options { margin-top:0; margin-right:calc(-6em - 8px); padding:0 4px; width:6em; display:flex; flex-direction:column; justify-content:center; }
  893. #loop_label input { margin:0px 4px 2px}
  894. #shuffle_label input { margin:2px 4px 0px}
  895. #audio_iframe { margin:0; padding:0; border:0; }
  896. /* CUE SHEET MENU */
  897. .cue_sheet_track_list_container { display:none; width:32px; background-image:${ SVG_UI_File_Icon("file_icon_playlist") }; background-position:center; background-repeat:no-repeat; background-size:18px; background-blend-mode:luminosity; background-color:inherit; }
  898. .cue_sheet_track_list { display:none; position:absolute; width:100%; margin:0; padding:0; left:0; right:0; z-index:1; overflow:auto; }
  899. #cue_sheet_track_list_container_audio .cue_sheet_track_list { top:33px; border-top:solid 7px transparent; }
  900. .cue_sheet_track_list_container:hover .cue_sheet_track_list { display:block; }
  901. .cue_sheet_track { list-style:none; display:grid; grid-gap:0; grid-template-columns:minmax(4em,6em) minmax(25%,75%) minmax(25%,75%) minmax(6em,8em); }
  902. .cue_sheet_track.header { font-weight:bold; cursor:default; }
  903. .cue_sheet_track:not(.header):hover { font-weight:bold; cursor:pointer; }
  904. .cue_sheet_track.selected { font-weight:bold; }
  905. .cue_sheet_track.selected .cue_track_id::before { content:""; width:16px; height:8px; display:inline-block; }
  906. .cue_sheet_track span { padding:4px 8px; font-variant-numeric:tabular-nums; }
  907. .cue_sheet_track span.cue_track_id { text-align:right; }
  908. .cue_sheet_track span.cue_index { text-align:right; }
  909. #cue_sheet_background { position:absolute; top:0; right:0; bottom:0; left:0; z-index:-1; }
  910. #cue_sheet_title { display:block; padding:4px 8px; font-variant-numeric:tabular-nums; text-align:center; }
  911. /* CONTENT TITLE PLAYLIST ENTRY (#content_playlist and #content_audio_playlist) */
  912. .playlist_entry_container { display:none; padding:4px 6px; text-align:center; flex-direction:row; }
  913. .playlist_entry_container textarea { width:100%; padding:0 6px; border:0; resize:vertical; }
  914. /***** CONTENT_CONTAINER *****/
  915. #content_container { box-sizing:border-box; justify-content:center; padding:0; position:relative; bottom:0; overflow:auto; width:100%; background-position:center; background-repeat:no-repeat; background-size: 50%; display:flex; flex-basis:100%; contain:strict; }
  916. .content { display:none; overflow:auto; width:100%; height:100% }
  917. /* CONTENT GRID (div) */
  918. #content_grid { display:none; position:absolute; padding:0; 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); }
  919. #content_grid::after { content:""; width:1px; position:absolute; top:0; right:0; bottom:0; }
  920. #content_grid a { display:block; }
  921. /* IMAGE GRID ITEMS */
  922. .image_grid_item { padding:6px; grid-column:auto; display:flex; align-items:center; justify-content:center; line-height:0; }
  923. .image_grid_item img { width:auto; max-width:${ ($settings.grid_image_size).toString() }px; max-height:${ ($settings.grid_image_size) }px; position:relative; }
  924. .image_grid_item img[src$=".svg"] { width: 100%; height:100%; }
  925. /* FONT GRID ITEMS */
  926. .font_grid_item { line-height:1; padding:8px 20px; grid-column: 1 / -1; }
  927. .font_grid_item p { margin:0; padding:0 0 6px 0; line-height:1; font-size:1rem; letter-spacing:0.1em; text-indent:0.1em; }
  928. .font_grid_item h2 { margin:0; font-weight:normal; font-size:${ $settings.grid_font_size * 4 }em; }
  929. .image_grid_item + .font_grid_item { margin-top:-1px; }
  930. /* CONTENT FONT.content */
  931. #content_font { hyphens:none; padding:0; position:relative; font-size:${ $settings.grid_font_size }em; overflow-wrap:break-word; }
  932. #font_specimen { max-width:100%; display:none; line-height:1.2; flex-direction:column; }
  933. #specimen { padding: 20px; font-size:4em; word-break:break-all; line-height:1.2; }
  934. #specimen_2 { margin:0; font-weight:normal; font-size:8em; overflow:hidden; text-overflow:ellipsis; white-space:pre; }
  935. #specimen_2H4 { margin:0; font-weight:normal; font-size:1.618em; overflow:hidden; text-overflow:ellipsis; }
  936. #specimen_3 { margin:0; font-weight:normal; font-size:6em; text-align:justify; overflow:hidden; text-overflow:ellipsis; white-space:pre; }
  937. #specimen_3H3 { margin:0; font-size:2em; hyphens:auto; }
  938. #specimen_string_2 { padding:20px; text-align:justify; hyphens:auto; }
  939. #specimen_string_3 { padding:20px; text-align:justify; }
  940. .lorem { text-align:justify; hyphens:auto; font-size:1em; line-height:1.4; column-gap:1.5em; overflow-wrap:normal; word-break:normal; }
  941. #lorem { padding:20px 20px 0; }
  942. #lorem::first-line { letter-spacing:0.1em; text-indent:0.1em; font-size:${ $settings.grid_font_size * 1.33 }em; font-variant:small-caps; }
  943. #lorem_2 { padding:12px 20px 0; columns:2; }
  944. #lorem_3 { padding:12px 20px 40px; columns:3; }
  945. /* FONT GLYPHS */
  946. #font_viewer { display:none; position:relative; font-family:unset; width:100%; }
  947. #content_pane[data-content="has_font_file"] #font_viewer { overflow-y:auto; }
  948. #content_pane[data-content="has_glyph"] #font_viewer { overflow:auto; }
  949. #content_pane[data-content="has_glyph"] #glyphs_container { visibility:hidden; }
  950. #glyphs_container { padding:0; position:relative; text-align:center; display:grid; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(120px,auto)); }
  951. .glyph_container { padding:0; position:relative; }
  952. .glyph_info { padding:2px; position:absolute; right:0; bottom:0; left:0; font-size:0.75rem; }
  953. #glyph_viewer { display:none; padding:0; z-index:1; position:absolute; top:0; right:0; bottom:0; left:0; background-color:#FFF; background-position:center; background-repeat:no-repeat; background-size:contain; }
  954. #glyph_viewer_info { padding:4px 6px; position:fixed; right:0; left:0; text-align:center; height:18px; line-height:1.6; font-size:0.875rem; }
  955. #content_font svg { width: 100%; }
  956. #save_svg_hidden { float:left; visibility:hidden; }
  957. #glyph_viewer_info div { padding:0; display:inline-block; }
  958. #glyph_viewer_info div::before { content:"Glyph "; }
  959. #save_svg { float:right; }
  960. #font_info { max-height:${ (window.innerHeight * 0.75) }px; font-size:0.875rem; position:fixed; bottom:0; z-index:2; }
  961. #font_info:hover { box-shadow:0px 4px 6px 3px #333; }
  962. #font_info th { padding:4px 6px 5px; letter-spacing:0.1em; text-indent:0.1em; }
  963. #font_info_body { display:none; max-height:${ ((window.innerHeight * 0.75) - 64) }px; font-size:0.875rem; overflow:auto; }
  964. .font_info_name { padding:4px 6px; text-align:right; font-weight:bold; width:33%; }
  965. .font_info_value { padding:4px 6px; width_66%; }
  966. .font_info_value a { font-weight:bold; }
  967. /* CONTENT TEXT EDITOR */
  968. #content_text { display:none; width:100%; max-width:100%; height:100%; overflow:hidden; padding:0; position:absolute; z-index:1; flex-direction:column; }
  969. /* CONTENT IMAGE.content */
  970. #content_image_container { display:none; margin:0; padding:2rem 2.5rem; position:relative; overflow:auto; box-sizing:border-box; }
  971. #content_image { margin:auto; width:auto; max-width:100%; max-height:100%; position:relative; object-fit:contain; cursor:zoom-in; }
  972. #content_pane.has_zoom_image #content_image_container { padding:0; }
  973. /* OTHER CONTENT ELEMENTS */
  974. #content_pdf { height:100%; padding:0; position:relative; width:100%; }
  975. #content_video { padding:0; position:absolute; background:transparent; }
  976. #content_iframe { display:none; width:100%; height:100%; padding:0; position:relative; background:white; border:0; }
  977. #content_iframe_utility { display:none; }
  978. /* WARNING STYLES */
  979. ${ $warning_styles }
  980. /* HELP STYLES */
  981. #help_container { display:none; padding: 0 1em 1em; overflow:auto; position:absolute; top:0; right:0; bottom:0; left:0; z-index:9998; contain:strict; }
  982. #help_container header { font-size:0.875rem; text-align:center; margin-right:calc(-1em - 2px); margin-left:calc(-1em - 2px); }
  983. #help_container header > span { display:inline-block; padding:6px; font-weight:bold; }
  984. #help_container tr { display:flex !important; border-bottom:solid 1px #666; }
  985. #help_container td { vertical-align:top; }
  986. #help_container dd { margin-inline-start:1em; }
  987. #help_container dd:before { content:"\u2219"; margin-right:6px; }
  988. #content_help { margin:1em auto; width:auto; overflow:auto; }
  989. #close_help { float:right; margin-left:-100% !important; margin:4px 6px; }
  990. #content_help tbody { font-size:0.875rem; padding:8px; overflow:auto; }
  991. #content_help td.kbd_shortcut { text-align:right; width:33%; padding:4px 9px 4px 6px; }
  992. #content_help td.help_description { width:66%; padding:4px 6px 4px 12px; }
  993. #content_help kbd { display:inline-block; min-width:1em; margin:2px; padding:2px 6px; border:solid 1px #888; border-radius: 3px; text-align:center; font-family:inherit; font-size:0.875em; }
  994. /* HANDLE & OVERLAY */
  995. #handle { position:absolute; top:0; bottom:0; z-index:1; cursor:col-resize; right:-4px; width:7px; }
  996. body.has_overlay #handle { z-index:9999; }
  997. body.has_warning::before, body.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; }
  998. `;
  999. var $color_and_background_styles = // added to #top and #iframe_body
  1000. `/* BACKGROUND COLORS */
  1001. /* body.theme_light .background_color_A0_20 { background-color: #A0A0A0; } */
  1002. /* body.theme_dark .background_color_A0_20 { background-color: #202020; } */
  1003. body.theme_light .background_color_B0_30, body.theme_light { background-color: #B0B0B0; }
  1004. body.theme_dark .background_color_B0_30, body.theme_dark { background-color: #303030; }
  1005. body.theme_light .background_color_C0_40 { background-color: #C0C0C0; }
  1006. body.theme_dark .background_color_C0_40 { background-color: #404040; }
  1007. body.theme_light .background_color_D0_50 { background-color: #D0D0D0; }
  1008. body.theme_dark .background_color_D0_50 { background-color: #505050; }
  1009. body.theme_light .background_color_E0_60 { background-color: #E0E0E0 !important; }
  1010. body.theme_dark .background_color_E0_60 { background-color: #606060 !important; }
  1011. body.theme_light .background_color_DD_44 { background-color: #DDDDDD; }
  1012. body.theme_dark .background_color_DD_44 { background-color: #383838; }
  1013. body.theme_light .background_color_DD_33, body.editor_theme_light .background_color_DD_33, body.theme_light.editor_theme_default .background_color_DD_33
  1014. { background-color: #DDDDDD; }
  1015. body.theme_dark .background_color_DD_33, body.editor_theme_dark .background_color_DD_33, body.theme_dark.editor_theme_default .background_color_DD_33
  1016. { background-color: #333333; }
  1017. body.theme_light .background_color_EE_22, body.editor_theme_light .background_color_DD_33:focus, body.theme_light.editor_theme_default .background_color_DD_33:focus, body.theme_light #content_container:hover, body.theme_light #content_grid::after
  1018. { background-color: #EEEEEE; }
  1019. body.theme_dark .background_color_EE_22, body.editor_theme_dark .background_color_DD_33:focus, body.theme_dark #content_container:hover, body.theme_dark #content_grid::after
  1020. { background-color: #222222; }
  1021. body.theme_light .background_color_FF { background-color: #FFFFFF; }
  1022. body.theme_dark .background_color_FF { background-color: #000000; }
  1023. body.theme_light .background_color_FF_11 { background-color: #EFEFEF; }
  1024. body.theme_dark .background_color_FF_11 { background-color: #0F0F0F; }
  1025. body.theme_light .background_color_EE_22:hover, body.theme_light .background_color_EE_22.hovered, body.theme_light .background_color_EE_22.selected { background-color: #FFFFFF; }
  1026. body.theme_dark .background_color_EE_22:hover, body.theme_dark .background_color_EE_22.hovered, body.theme_dark .background_color_EE_22.selected { background-color: #000000; }
  1027. body.theme_light .background_color_22_EE { background-color: #222222; }
  1028. body.theme_dark .background_color_22_EE { background-color: #EEEEEE; }
  1029. body.theme_light .background_color_11_FF, body.theme_light .background_color_22_EE:hover, body.theme_light .background_color_22_EE.hovered { background-color: #0F0F0F; }
  1030. body.theme_dark .background_color_11_FF, body.theme_dark .background_color_22_EE:hover, body.theme_dark .background_color_22_EE.hovered { background-color: #EFEFEF; }
  1031. /* DIR LIST ROWS: .alternate_background */
  1032. body.theme_dark.alternate_background #tbody tr:nth-of-type(odd), body.theme_dark.alternate_background .cue_sheet_track:not(.header):nth-of-type(odd) { background-color: #505050; }
  1033. body.theme_light.alternate_background #tbody tr:nth-of-type(odd), body.theme_light.alternate_background .cue_sheet_track:not(.header):nth-of-type(odd) { background-color: #D0D0D0; }
  1034. body.theme_dark.alternate_background #tbody tr:nth-of-type(even), body.theme_dark.alternate_background .cue_sheet_track:not(.header):nth-of-type(even) { background-color: #404040; }
  1035. body.theme_light.alternate_background #tbody tr:nth-of-type(even), body.theme_light.alternate_background .cue_sheet_track:not(.header):nth-of-type(even) { background-color: #E8E8E8; }
  1036. body.theme_dark #content_help tr:nth-of-type(even) { background-color: #484848; }
  1037. body.theme_light #content_help tr:nth-of-type(even) { background-color: #CCCCCC; }
  1038. /* NON-MEDIA ROWS .selected, .loaded, :hover ("light cyan") */
  1039. body.theme_light:not(.has_stats) tr:not(.media).selected, body.theme_light:not(.has_stats) tr.content_loaded:not(video), body.theme_light:not(.has_stats) tr.content_loaded:not(.video):hover, body.theme_light #menu li.selected
  1040. { background-color: rgba(172,202,235,1.00) !important; }
  1041. body.theme_light:not(.has_stats) tr.content_loaded:not(.video), body.theme_light:not(.has_stats) #tbody tr:not(.media):hover, body.theme_light.alternate_background:not(.has_stats) #tbody tr:not(.media):hover, body.theme_light tr:not(.media).hovered
  1042. { background-color: rgba(172,202,235,0.60) !important; }
  1043. body.theme_dark:not(.has_stats) tr:not(.media).selected, body.theme_dark:not(.has_stats) tr.content_loaded:not(.video), body.theme_dark:not(.has_stats) tr.selected.content_loaded:not(.video):hover, body.theme_dark #menu li.selected
  1044. { background-color: rgba(101,140,179,0.80) !important; } /* #658CB3 */
  1045. body.theme_dark:not(.has_stats) tr:not(.video).content_loaded, body.theme_dark:not(.has_stats) #tbody tr:not(.media):hover, body.theme_dark.alternate_background:not(.has_stats) #tbody tr:not(.media):hover, body.theme_dark tr:not(.media).hovered
  1046. { background-color: rgba(101,140,179,0.60) !important; }
  1047. /* MEDIA ROWS .audio_loaded, .video.content_loaded, .selected, :hover */
  1048. body.theme_light:not(.has_stats) tr.media[class*="_loaded"], body.theme_light .cue_sheet_track.selected { background-color: rgba(130,196,196,1) !important; } /* #82C4C4 */
  1049. body.theme_light:not(.has_stats) tr.media.selected:not([class*="_loaded"]), body.theme_light tr.media.hovered, body.theme_light .cue_sheet_track:not(.header):hover
  1050. { background-color: rgba(116,190,190,0.60) !important; }
  1051. body.theme_light:not(.has_stats) tr.media:not([class*="_loaded"]):hover { background-color: rgba(116,190,190,0.40) !important; }
  1052. body.theme_dark:not(.has_stats) tr.media[class*="_loaded"], body.theme_dark .cue_sheet_track.selected { background-color: rgba(076,143,143,0.75) !important; }
  1053. body.theme_dark:not(.has_stats) tr.media.selected:not([class*="_loaded"]), body.theme_dark tr.media.hovered, body.theme_dark .cue_sheet_track:not(.header):hover
  1054. { background-color: rgba(076,143,143,0.55) !important; }
  1055. body.theme_dark:not(.has_stats) tr.media:hover { background-color: rgba(076,143,143,0.45) !important; }
  1056. /* text editor row ("purple") */
  1057. body.theme_light #text_editor_row.has_text_editor, body.theme_light.edited #text_editor_row { background-color: rgba(160,160,230,1.00); } /* #A0A0E6 */
  1058. body.theme_dark #text_editor_row.has_text_editor, body.theme_dark.edited #text_editor_row { background-color: rgba(100,100,160,1.00); } /* #6464A0 */
  1059. /* menu items :hover, with exceptions for default text editor view */
  1060. body.theme_dark .menu li:hover, body.theme_dark .menu li#preview_text_menu_item span:hover { background-color: #686868; }
  1061. body.theme_light .menu li:hover, body.theme_light .menu li#preview_text_menu_item span:hover { background-color: #B8B8B8; }
  1062. #preview_text_menu_item:hover { background-color: initial; }
  1063. /* BACKGROUND IMAGES */
  1064. .has_background, .has_background_before::before, .has_background_after::after { background-repeat:no-repeat; background-position:center; background-color:transparent !important; }
  1065. body.theme_light .bookmark > a::before { background-image: ${ SVG_UI_Icon("bookmark") }; }
  1066. body.theme_dark .bookmark > a::before { background-image: ${ SVG_UI_Icon("bookmark_dark") }; }
  1067. body.theme_light li.has_submenu { background-image: ${ SVG_UI_Icon("arrow") }; }
  1068. body.theme_dark li.has_submenu { background-image: ${ SVG_UI_Icon("arrow_dark") }; }
  1069. .background_color_check_mark::before, #theme::before, body.sort_by_default #default span::before, body.sort_by_name #name span::before, body.sort_by_time #time span::before, body.sort_by_size #size span::before, body.sort_by_date #date span::before, body.sort_by_kind #kind span::before, body.sort_by_ext #ext span::before, body.alternate_background #alternate_background span::before, body.show_numbers #show_numbers span::before, body.autoload_media #autoload_media span::before, body.autoload_index_files #autoload_index_files span::before, body.play_all_media #play_all_media span::before, body.split_view #split_view_menu_item, body.split_view #split_view span::before, body.source_text #source_text::before, body.preview_text #preview_text::before, body.sort_by_default #sort_by_default span::before, body.loop_media #loop_media_menu::before, body.shuffle_media #shuffle_media_menu::before, body.sort_by_name #sort_by_name span::before, body.sort_by_time #sort_by_time span::before, body.sort_by_size #sort_by_size span::before, body.sort_by_date #sort_by_date span::before, body.sort_by_kind #sort_by_kind span::before, body.sort_by_ext #sort_by_ext span::before, body.hide_ignored_items #hide_ignored_items span::before, body.ignore_ignored_items #ignore_ignored_items span::before, body.editor_theme_default #editor_theme_default::before, body.editor_theme_light #editor_theme_light::before, body.editor_theme_dark #editor_theme_dark::before, #enable_text_editing::before, body.source_text #source_text::before, body.preview_text #preview_text::before, body.preview_html #preview_html::before, .cue_sheet_track.selected .cue_track_id::before
  1070. { background-image:${ SVG_UI_Icon("check_mark") }; }
  1071. body.sort_by_default #sort_by_default span::after, body.sort_by_name #sort_by_name span::after, body.sort_by_time #sort_by_time span::after, body.sort_by_size #sort_by_size span::after, body.sort_by_date #sort_by_date span::after, body.sort_by_kind #sort_by_kind span::after, body.sort_by_ext #sort_by_ext span::after { background-image: ${ SVG_UI_Icon("chevron_down") }; }
  1072. #parent_dir_nav a { background-image: ${ SVG_UI_Icon("chevron_up") }; }
  1073. body.theme_light #content_pane[data-content="has_ignored"] #content_container { background-image: ${ SVG_UI_Icon("ignored") }; background-size:25%; }
  1074. body.theme_dark #content_pane[data-content="has_ignored"] #content_container { background-image: ${ SVG_UI_Icon("ignored_dark") }; background-size:25%; }
  1075. body.has_audio #content_pane:not([data-content^="has_"]):not([data-loaded="unloaded"]) #content_container
  1076. { background-image:${ SVG_UI_Icon("music") }; }
  1077. body.is_error #content_container { background-image: ${ SVG_UI_Icon("error") }; }
  1078. ${ CSS_UI_Icon_Rules() }
  1079. body.theme_light #grid_btn:not(.has_grid), body.theme_light #grid_btn:not(.has_grid) .menu { background-image:${ SVG_UI_Icon("grid") }; }
  1080. body.theme_dark #grid_btn:not(.has_grid), body.theme_dark #grid_btn:not(.has_grid) .menu { background-image:${ SVG_UI_Icon("grid_dark") }; }
  1081. body.theme_light #grid_btn.has_grid, body.theme_light #grid_btn.has_grid .menu { background-image: ${ SVG_UI_Icon("grid_loaded") }; }
  1082. body.theme_dark #grid_btn.has_grid, body.theme_dark #grid_btn.has_grid .menu { background-image: ${ SVG_UI_Icon("grid_loaded_dark") }; }
  1083. #current_dir_path span::before { background-image: ${ SVG_UI_File_Icon("file_icon_dir") }; float:none; display:inline-block; margin:0 0 -2px 0; width:24px; }
  1084. body.has_playlist #current_dir_path span::before { background-image: ${ SVG_UI_File_Icon("file_icon_playlist") }; float:none; display:inline-block; margin:0 0 -2px 0; width:24px; }
  1085. body.is_error #current_dir_path span::before { background-image: ${ SVG_UI_Icon("error") }; float:none; display:inline-block; margin:0 0 -2px 0; width:24px; }
  1086. #content_pane[data-content="has_text_editor"] #title span::before { background-image: ${ SVG_UI_File_Icon("file_icon_markdown") }; }
  1087. #content_pane[data-content="has_font_file"] #title span::before { background-image: ${ SVG_UI_File_Icon("file_icon_font") }; }
  1088. #content_audio_title span::before { background-image: ${ SVG_UI_File_Icon("file_icon_audio") }; height:14px !important; }
  1089. #content_pane[data-content="has_grid"] #title span::before { background-image: ${ SVG_UI_File_Icon("file_icon_dir") }; height:14px !important; }
  1090. #content_pane[data-content="has_view_directory_source"] #title span::before { background-image: ${ SVG_UI_File_Icon("file_icon_dir_default") }; height:14px !important;background-size:contain;}
  1091. #dir_list tr.ignored.local a.icon span.tbody_row_cell_name_a_span::after { content: "\\00a0[local file]"; display:contents; font-style:italic; }
  1092. /* BORDERS */
  1093. { border-top:solid 1px #666 !important; font-weight:bold; }
  1094. body.theme_dark .border_all { border: solid 1px #111; }
  1095. body.theme_light .border_all { border: solid 1px #666; }
  1096. body.theme_dark .border_top { border-top: solid 1px #111; }
  1097. body.theme_light .border_top { border-top: solid 1px #666; }
  1098. body.theme_dark .border_right { border-right: solid 1px #111; }
  1099. body.theme_light .border_right { border-right: solid 1px #666; }
  1100. body.theme_dark .border_bottom { border-bottom: solid 1px #111; }
  1101. body.theme_light .border_bottom { border-bottom: solid 1px #666; }
  1102. body.theme_dark .border_left { border-left: solid 1px #111; }
  1103. body.theme_light .border_left { border-left: solid 1px #666; }
  1104. .border_top_x { border-top: solid 1px #666; } /* "x" = inverted for theme_dark */
  1105. .border_right_x { border-right: solid 1px #666; }
  1106. .border_bottom_x { border-bottom: solid 1px #666; }
  1107. .border_left_x { border-left: solid 1px #666; }
  1108. .split_btn::after { border-left: solid 1px #333; }
  1109. /* TEXT COLORS */
  1110. body.theme_light a.text_color_111:hover, body.theme_light .text_color_111.selected, body.theme_light .selected .text_color_111 { color: #000; }
  1111. body.theme_dark a.text_color_111:hover, body.theme_dark .text_color_111.selected, body.theme_dark .selected .text_color_111 { color: #FFF; }
  1112. body.theme_light .text_color_111, body.editor_theme_light #content_text .text_color_111, body.theme_light #help_container, body.theme_light #help_container a { color: #111; }
  1113. body.theme_dark .text_color_111, body.editor_theme_dark #content_text .text_color_111, body.theme_dark #help_container, body.theme_dark #help_container a { color: #EEE; }
  1114. body.theme_light .text_color_333 { color: #333; }
  1115. body.theme_dark .text_color_333 { color: #CCC; }`
  1116. ;
  1117. var $conditional_styles = `
  1118. /* PSEUDO-ELEMENTS */
  1119. #content_pane[data-content="has_image"] #title span::after { content: attr(data-after); }
  1120. #content_pane[data-content="has_font"] #title::before { content: "Font:" }
  1121. #content_pane[data-content="has_font_file"] #title::before { content: "Glyphs from font:" }
  1122. body.has_directory_source #title::before { content: "Source of: " }
  1123. #content_pane[data-content="has_grid"] #title::before { content: "Fonts and Images from:"; }
  1124. #content_pane[data-content="has_grid"].has_font_grid #title::before { content: "Fonts from:"; }
  1125. #content_pane[data-content="has_grid"].has_image_grid #title::before { content: "Images from:"; }
  1126. #content_pane[data-content="has_grid"] #title::after { content: "${ current_dir }"; }
  1127. #content_pane[data-content="has_ignored"] #title::before { content: "Ignored content:"; }
  1128. #content_pane[data-content="has_dir"] #title::before { content: "Index of:"; }
  1129. #content_pane[data-loaded="unloaded"] #content_container { background-image:${SVG_UI_Icon("spinner")}; background-repeat:no-repeat; background-position:center;background-size:32px;}
  1130. body #sidebar_title div:before { content: "INDEX OF"; }
  1131. body.has_playlist #sidebar_title div:before { content: "PLAYLIST"; }
  1132. body.has_filelist #sidebar_title div:before { content: "FILELIST"; }
  1133. #content_pane.has_audio #content_audio_title span::before, body #content_pane[data-content="has_video"] #title::before
  1134. { content: "Playing: "; }
  1135. #reload_btn.reset::before, #content_pane.has_zoom_image #reload_btn::before, #content_pane.has_scaled_image #reload_btn::before { content: "Reset"; }
  1136. #content_pane[data-content="has_text_editor"] #close_btn::before { content: "Hide"; }
  1137. #content_pane[data-content="has_text_editor"] #title span::after { content: "Text Editor"; }
  1138. #content_pane[data-content="has_text_editor"].edited #title span::after { content: "Text Editor (edited)"; }
  1139. body.source_text:not(.split_view) #content_pane[data-content="has_text_editor"] #title::after { content: " (Source Text)"; }
  1140. body.preview_text:not(.split_view) #content_pane[data-content="has_text_editor"] #title::after { content: " (Text Preview)"; }
  1141. body.preview_html:not(.split_view) #content_pane[data-content="has_text_editor"] #title::after { content: " (HTML Preview)"; }
  1142. body.edited #text_editor_row a:after, body.iframe_edited:not(.has_text_editor) #content_pane.has_iframe #title::after
  1143. { content: " (edited)"; }
  1144. body.theme_light #theme span::before { content: "Light "; }
  1145. body.theme_dark #theme span::before { content: "Dark "; }
  1146. body.show_details #show::before { content: "Hide "; }
  1147. #disable::after { content: "Enabled"; }
  1148. body.disable_text_editing #disable::after { content: "Disabled"; }
  1149. #disable::after { content: "Enabled"; }
  1150. body.is_error #sidebar_header_body { border-bottom:0; }
  1151. body.is_error #title span::before { content:"ERROR"; width:auto; }
  1152. #dir_list #tbody tr#is_error { display:block !important; grid:none !important; grid-template-columns:none !important; }
  1153. #dir_list #tbody tr#is_error > td { padding:6px 8px; }
  1154. #stats_details span.ignored::before,#stats_details span.invisible::before { content:"("; }
  1155. #stats_details span.ignored::after,#stats_details span.invisible::after { content:")"; }
  1156. #stats_details tr.audio a span span::after { content:attr(data-audio_duration); white-space:pre; }
  1157. #stats_details tr.video a span span::after { content:attr(data-video_duration); white-space:pre; }
  1158. /* DISPLAY */
  1159. body.hide_sidebar #handle, body.has_stats #footer_links, body.is_error #sidebar_header_body > div:not(:first-of-type), body.is_error #tfoot, body.is_non_local #show_invisibles_container, tr.invisible, tr.ignored, body.hide_ignored_items #tbody tr.ignored, body.hide_ignored_items #tbody tr.ignored, body.hide_ignored_items:not(.show_invisibles) #tbody tr.ignored.invisible, body.hide_ignored_items:not(.show_invisibles) #tbody tr.ignored.invisible, body:not(.show_invisibles) #tbody tr.invisible, body:not(.has_media) #time, #close_playlist_container
  1160. { display:none; }
  1161. body.has_media #play_toggle, .media .tbody_row_cell_name_a_span input, body.theme_dark #theme_dark, body.theme_light #theme_light, body.show_details .tbody_row_cell_details, #content_pane[class^="has_"] #close_btn, #content_pane[data-content="has_text_editor"] #close_btn
  1162. { display:unset; }
  1163. #font_info:hover #font_info_body { display:table-row-group; }
  1164. body.has_playlist #stats_summary_playlist_files, body.has_filelist #stats_summary_playlist_files, body.has_help #help_container
  1165. { display:table-row; }
  1166. body.has_images #grid_btn, body.has_fonts #grid_btn { display:table-cell; }
  1167. body.has_menu #menu, body.has_menu_parents #parents_links, #sidebar_menus .has_submenu:hover .submenu, #menu .has_submenu.hovered .submenu, #footer_links:hover ul, body.has_images.has_fonts #grid_btn:hover ul.menu, #content_pane[data-content="has_font_file"] #font_viewer, #content_pane[data-content="has_glyph"] #font_viewer, #tfoot tr, body.has_warning #overlay_container, .cue_sheet_track_list_container.has_cue_sheet, body.has_playlist #close_playlist_container, body.has_filelist #close_playlist_container
  1168. { display:block; }
  1169. body.show_details #dir_list td.details, body.show_numbers .tbody_row_cell_name_a::before, #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_right .split_btn, #content_pane[data-content="has_htm"] #open_in_text_editor
  1170. { display:inline-block; }
  1171. #tbody tr:not(.ignored):not(.invisible), body.show_invisibles #tbody tr.invisible, body.has_stats #tbody tr.invisible, body.has_stats #tbody tr.ignored, body.has_stats #tbody tr.ignored.hovered, body.hide_ignored_items.has_stats #tbody tr.ignored, body:not(.hide_ignored_items) #tbody tr.ignored:not(.invisible)
  1172. { display:grid; }
  1173. body.show_details #sorting_row_2, body.show_details #text_editor_row, #text_editor_row.has_text_editor, #content_pane.has_audio #content_audio_title, #content_pane.has_audio #content_audio, .playlist_entry_container.has_content, #content_pane .content.has_content:not(#content_iframe), #content_pane[data-loaded="loaded"] #content_iframe, #content_pane[data-content="has_text_editor"] #content_text, #content_pane[data-content="has_font"] #font_specimen, #content_pane[data-content="has_image"] #content_container, #content_pane[data-content="has_image"] #content_image_container, #content_pane[data-content="has_video"] #content_container, body.has_playlist #close_playlist_btn_container, body.has_filelist #close_playlist_btn_container
  1174. { display:flex; }
  1175. #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
  1176. { display:none !important; }
  1177. body.theme_dark .invert, body.theme_dark #menu li > span::before, body.theme_dark #sorting span::before, body.theme_dark #sorting span::after
  1178. { filter:invert(1); }
  1179. #content_pane[data-content="has_ignored"]::before { opacity:0.3; }
  1180. body.has_warning #sidebar_wrapper, body.has_warning #content_pane, #close_audio:hover::after, body.has_menu_parents #tbody, body.has_menu #tbody, body.faded:not(.has_stats) #tbody tr:not(.hovered), body.has_stats:not(.faded) #tbody tr:not(.hovered), #parent_dir_menu:not(:hover), #menu_container:not(:hover) nav, #hide_sidebar, #grid_btn, #dir_list.has_dir:not(:hover), #footer_links, .split_btn span, .disabled:not(.local), body.focus_content #tbody tr:not(.hovered)
  1181. { opacity: 0.6; }
  1182. #grid_btn:hover, #prev_next_btns span:hover, #hide_sidebar:hover, #grid_btn:hover, #footer_links:hover, .split_btn span:hover
  1183. { opacity: 1.0; }
  1184. /* HAS HIDDEN SIDEBAR */
  1185. body.hide_sidebar #hide_sidebar { left:2px; transform:rotate(180deg); }
  1186. body.hide_sidebar #sidebar_wrapper { width:0 !important; min-width:0; position:absolute; top:2px; left:-1px; }
  1187. body.hide_sidebar #sidebar_header { z-index:unset; display:none; }
  1188. body.hide_sidebar #sidebar { visibility:hidden; } /* allows hidden sidebar to still be navigated by arrows */
  1189. body.hide_sidebar #dir_list { min-width:0; }
  1190. body.hide_sidebar #content_pane { width:100% !important; }
  1191. body.hide_sidebar #title_buttons_left { padding-left:24px; }
  1192. /* HAS GRID */
  1193. #content_pane[data-content="has_grid"] #content_grid { display: grid; }
  1194. #content_pane.has_hidden_grid #content_grid { max-height:100%; display:grid; overflow:hidden; }
  1195. /* HAS ZOOM IMAGE */
  1196. #content_pane.has_scaled_image #content_image_container { display:grid; padding:0; }
  1197. #content_pane.has_zoom_image #content_image { width:unset; max-width:unset; max-height:unset; cursor: zoom-out; }
  1198. `;
  1199. var $text_editor_styles = `
  1200. html, body, #iframe_body { margin:0; padding:0; height:100%; position:relative; font-family:${ $settings.UI_font }; font-size: ${ $settings.UI_font_size }; }
  1201. button.focus, button:focus { outline:none; border-radius:3px !important; border-style:solid !important; border-width:1px !important; border-color:#222 !important; }
  1202. #iframe_body { display:flex; flex-direction:column; position:relative; overflow:hidden; }
  1203. #iframe_body #content_text { display:flex; height:100%; padding:0; position:absolute; z-index:1; width:100%; overflow:hidden; flex-direction:column; }
  1204. body.editor_theme_dark .border_right { border-right: solid 1px #111; }
  1205. .editor_theme_light .border_right { border-right: solid 1px #666; }
  1206. body.editor_theme_dark .border_bottom { border-bottom: solid 1px #111; }
  1207. .editor_theme_light .border_bottom { border-bottom: solid 1px #666; }
  1208. ul.has_popout_menu, .editor_theme_light #toolbar ul.has_popout_menu { background-color:#C0C0C0; border:solid 1px #666; }
  1209. ul.has_popout_menu link { background-color:#D0D0D0; }
  1210. ul.has_popout_menu li:hover { background-color:#E0E0E0; }
  1211. body.editor_theme_dark #toolbar ul.has_popout_menu li { background-color:#C0C0C0; }
  1212. ${ $warning_styles }
  1213. /* TOOLBAR */
  1214. #toolbar { width:100%; background:#C0C0C0; position:relative; z-index:100; border-bottom: solid 1px #999; border-collapse:collapse; font-family:${ $settings.UI_font }; 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; position:relative; }
  1215. #toolbar td { padding:2px 0 0; }
  1216. #toolbar_buttons { margin:0; padding:0; list-style:none; }
  1217. .toolbar_icon { margin:0 4px; padding:4px; width:16px; height:16px; float:left; background-size:14px; background-repeat:no-repeat; background-position:center; cursor:pointer; display:block; opacity:0.5; color:#444; }
  1218. #editor_theme { background-image:${ SVG_Text_Editing_UI_Icon("toggle_theme") }; }
  1219. #toggle_source_text { background-image:${ SVG_Text_Editing_UI_Icon("show_markdown") }; background-size:18px; }
  1220. #toggle_preview_text { background-image:${ SVG_Text_Editing_UI_Icon("show_preview") }; }
  1221. #toggle_preview_html { background-image:${ SVG_Text_Editing_UI_Icon("show_html") }; background-size:20px; }
  1222. #toggle_split_view { background-image:${ SVG_Text_Editing_UI_Icon("toggle_split") }; }
  1223. #sync_scroll { padding:4px; float:left; opacity:1 !important; }
  1224. #sync_scroll input { float:left; }
  1225. #sync_scroll label { width:8em; display:block; font-size:inherit; line-height:1.5; }
  1226. #save_btn { margin:0; padding:4px 4px 4px 8px; width:20px; height:16px; float:right; position:relative; z-index:9997; background-repeat:no-repeat; background-position:center right 7px; background-image:${ SVG_Text_Editing_UI_Icon("save_btn") }; background-size:16px; cursor:pointer; outline:none; }
  1227. #save_btn ul { display:none; margin:0; padding:0 32px 0 0; position:absolute; top:-4px; right:-2px; list-style:none; box-shadow:0px 4px 6px -3px #333; background-repeat:no-repeat; background-position:right 8px top 7px; background-image:${ SVG_Text_Editing_UI_Icon("save_btn") }; background-size:16px; background-color:#C0C0C0; }
  1228. .edited #save_btn, .edited #save_btn ul { background-image:${ SVG_Text_Editing_UI_Icon("save_btn_edited") }; }
  1229. #save_btn ul li { margin:0; padding:4px 6px; text-align:right; width:100%; white-space:pre; box-sizing:border-box; }
  1230. #save_btn a { font-weight:normal; }
  1231. #save_btn:hover ul { display:block; }
  1232. #save_btn li:hover { background-color:#DDDDDD !important; }
  1233. body.editor_theme_dark #save_btn ul { border-color:#EEE; box-shadow:0px 4px 6px -3px #EEE;}
  1234. body.editor_theme_dark #save_btn li { border-color:#EEE; color:#111; background-color:#AEAEAE !important; }
  1235. body.editor_theme_dark #save_btn li:hover { background-color:#989898 !important; }
  1236. body.editor_theme_dark #save_text { border-color:#EEE; color:#111; }
  1237. #clear_text { margin:0 4px; padding:4px; height:16px; float:right; cursor:pointer; }
  1238. #toolbar li:hover, body.source_text #toggle_source_text, body.split_view #toggle_source_text, body.split_view:not(.preview_html) #toggle_preview_text, body.preview_text #toggle_preview_text, body.preview_html #toggle_preview_html, .split_view #toggle_split_view, .edited #save_btn
  1239. { opacity:1; }
  1240. /* TEXT CONTENT CONTAINERS */
  1241. #text_container { display:flex; flex-grow:1; overflow:hidden; }
  1242. #text_source, #text_preview, #html_preview { margin:0; padding:1em; display:none; 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*/,'') }; }
  1243. #text_preview { padding:0; }
  1244. #text_source { border-right-width:1px; resize:none; font-family:monospace; }
  1245. #html_preview { white-space:pre-wrap; word-break:break-word; font-family:monospace; }
  1246. /* TEXT SOURCE */
  1247. #text_source:focus, #text_preview:focus, #html_preview:focus { outline:none; }
  1248. #text_source:focus { box-shadow: inset 0 0 4px #666; }
  1249. body.editor_theme_dark #text_source:focus, body.theme_dark.editor_theme_default #text_source:focus { box-shadow: inset 0 0 6px #000; }
  1250. /* SPLIT VIEWS */
  1251. .split_view #text_source, .split_view:not(.preview_html) #text_preview, .split_view.preview_html #html_preview { width:50%; display:block; }
  1252. body.preview_text:not(.split_view):not(.preview_html) #text_preview, body.source_text:not(.split_view) #text_source, body.preview_html:not(.split_view) #html_preview
  1253. { display:block; }
  1254. body.source_text.preview_text:not(.split_view) #text_preview, body.source_text.preview_html:not(.split_view) #html_preview
  1255. { display:none !important; }
  1256. /* TEXT PREVIEW */
  1257. body.editor_theme_dark #toolbar, body.theme_dark.editor_theme_default #toolbar { background: #404040; color:#EEEEEE; border-bottom: solid 1px #111; }
  1258. body.editor_theme_dark #toolbar .has_background:not(#save_btn), body.editor_theme_dark:not(.edited) #save_btn, body.editor_theme_dark.edited #save_btn li,
  1259. body.theme_dark.editor_theme_default #toolbar .has_background:not(#save_btn), body.theme_dark.editor_theme_default:not(.edited) #save_btn, body.theme_dark.editor_theme_default.edited #save_btn li, body.editor_theme_dark #text_preview
  1260. { filter: invert(1); }
  1261. body.theme_light.editor_theme_default .background_color_DD_33, body.editor_theme_light .background_color_DD_33 { background-color: #DDDDDD; }
  1262. body.editor_theme_dark .background_color_DD_33, body.theme_dark.editor_theme_default .background_color_DD_33 { background-color: #333333; }
  1263. body.theme_light.editor_theme_default .background_color_EE_22, body.editor_theme_light .background_color_DD_33:focus, body.theme_light #content_container:hover, body.theme_light #content_grid::after, #text_preview table th, body.editor_theme_light #text_container, #top:not(.editor_theme_dark) #text_container
  1264. { background-color: #F6F6F6; }
  1265. body.theme_dark.editor_theme_default .background_color_EE_22, body.editor_theme_dark .background_color_DD_33:focus, body.theme_dark.editor_theme_default .background_color_DD_33:focus, body.theme_dark #content_container:hover, body.theme_dark #content_grid::after, body.editor_theme_dark #text_container
  1266. { background-color: #222222; }
  1267. /* custom previewed text styles */
  1268. #text_preview 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*/,'') }; }
  1269. #text_preview th, #text_preview td { vertical-align:top; }
  1270. #text_preview blockquote { margin-top:1em; margin-bottom:1em; color: #555; }
  1271. #text_preview blockquote + blockquote { margin-top:0; }
  1272. .markdown-body input[type="checkbox"] { margin-top:0.375em; margin-right:6px; float:left; }
  1273. h1 .uplink,h2 .uplink,h3 .uplink,h4 .uplink,h5 .uplink,h6 .uplink
  1274. { display:inline-block; font-size:0.875em; transition: opacity 0.25s; opacity:0; cursor:pointer; margin:0; padding:0; }
  1275. h1:hover .uplink,h2:hover .uplink,h3:hover .uplink,h4:hover .uplink,h5:hover .uplink,h6:hover .uplink
  1276. { transition: opacity 0.25s; opacity:0.5; }
  1277. #text_preview table { font-size:inherit; }
  1278. .markdown-body table tr, .markdown-body .highlight pre, .markdown-body pre { background-color:transparent !important; }
  1279. .markdown-body::before, .markdown-body::after { display:none !important; background:transparent; }
  1280. /* disabled text editing */
  1281. li#save_btn div, .comment, #iframe_body.disable_text_editing #toolbar, #iframe_body.disable_text_editing #preview_text, #iframe_body.disable_text_editing #text_editing_handle
  1282. { display:none; }
  1283. #iframe_body.disable_text_editing #text_source.disabled { top:0; background:white; }
  1284. #iframe_body.editor_theme_light .background_color_D0_50, #iframe_body.theme_light.editor_theme_default .background_color_D0_50 { background-color: #D0D0D0; }
  1285. #iframe_body.editor_theme_dark .background_color_D0_50, #iframe_body.theme_dark.editor_theme_default .background_color_D0_50 { background-color: #505050; }
  1286. #iframe_body.editor_theme_light .background_color_E0_60, #iframe_body.theme_light.editor_theme_default .background_color_E0_60 { background-color: #E0E0E0; }
  1287. #iframe_body.editor_theme_dark .background_color_E0_60, #iframe_body.theme_dark.editor_theme_default .background_color_E0_60 { background-color: #606060; }
  1288. #iframe_body.editor_theme_light .text_color_111, #iframe_body.editor_theme_light #content_text .text_color_111, #iframe_body.theme_light.editor_theme_default .text_color_111, #iframe_body.theme_light.editor_theme_default #content_text .text_color_111 { color: #111; }
  1289. #iframe_body.editor_theme_dark .text_color_111, #iframe_body.editor_theme_dark #content_text .text_color_111 { color: #EEE; }
  1290. #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; }
  1291. /* TEXT EDITOR RESIZE HANDLE */
  1292. #text_editing_handle { display:none; width:8px; position:absolute; top:0; bottom:0; left:calc(50% - 4px); cursor:col-resize; z-index:3; }
  1293. #text_editing_handle::before { content:""; width:1px; background:#666; position:absolute; top:0; bottom:0; left:calc(50%); }
  1294. #iframe_body.editor_theme_dark #text_editing_handle::before { background:#111; }
  1295. .split_view #text_editing_handle { display:block; }
  1296. `;
  1297. var $iframe_dir_styles = `
  1298. :root, html, body { margin:0; padding:0; width:100%; max-width:100%; height:100%; border:0; border-radius:0; font-family:${ $settings.UI_font }; font-size:${ $settings.UI_font_size }; hyphens:auto; overflow:hidden; }
  1299. #iframe_body { width:100%; display:flex; flex-direction:column; font-size:${ (parseFloat($settings.UI_font_size) * 0.875) + $settings.UI_font_size.replace(/\d*/,'') }; }
  1300. #iframe_body.theme_dark { background-color:#333; }
  1301. a, a:hover { text-decoration: none !important; }
  1302. /* IFRAME HEADER */
  1303. #thead { flex-direction:row; font-size: 0.875rem; user-select:none; -webkit-user-select:none; }
  1304. #change_dirs { display:flex; flex-direction:row; justify-content:space-between; white-space:pre; }
  1305. #change_dirs a { padding-right:0 !important; padding-left:0 !important;}
  1306. #change_dirs span:hover { font-weight:bold; }
  1307. #parent { padding:5px 3px 5px 0; text-align:left; flex-grow:1; }
  1308. #open_in_sidebar { padding:5px 0 5px 3px; text-align:right; flex-grow:1; }
  1309. #parent a::before, #open_in_sidebar a::after { content:""; width:16px; height:10px; display:inline-block; }
  1310. #open_in_sidebar a::after { background-image:${ SVG_UI_Icon('chevron_left') }; background-repeat: no-repeat; background-size:6px; background-position: center; }
  1311. .theme_dark #parent a::before, .theme_dark #open_in_sidebar a::after { filter:invert(1); }
  1312. /* IFRAME SORTING */
  1313. #sorting_row_1 div, #sorting_row_2 div { flex-grow:1; padding:0; }
  1314. #sorting_row_1 { display:flex; cursor:pointer; justify-content:space-between; flex-direction:row; }
  1315. #sorting_row_1 div { width:50%; }
  1316. #sorting_row_2 { overflow-x:scroll; }
  1317. .sorting span { display:inline-block; padding:3px 0; }
  1318. .sorting:hover span { font-weight:bold; }
  1319. .sorting span::before, .sorting span::after { content:""; width:16px; height:8px; display:inline-block; position:relative; color:#CCC; background-repeat:no-repeat; background-position:center; background-size:10px; }
  1320. .sorting.down span::after { transform:rotate(180deg) }
  1321. .theme_dark .sorting span::before, .theme_dark .sorting span::after { filter:invert(1); }
  1322. #sort_by_name, #sort_by_ext { text-align:left; }
  1323. #sort_by_time, #sort_by_size, #sort_by_date, #sort_by_default, #sort_by_kind { text-align:right; }
  1324. body:not(.has_media) #sort_by_time { display:none; }
  1325. body.sort_direction_up .sorting span::after { transform:rotate(180deg) !important; }
  1326. /* IFRAME DIR LIST */
  1327. #iframe_dir_list_wrapper { width:100%; height:100%; display:block; position:relative; overflow:auto; margin-bottom:-1px; }
  1328. #dir_list { width:100%; height:100%; line-height:1.5; position:relative; overflow:hidden; border-collapse:collapse; }
  1329. #tbody { font-size:0.875rem; counter-reset:row; overflow:hidden; display:block; }
  1330. #tbody tr, #sorting_row_2 { display: grid; grid-gap:0; grid-template-columns: minmax(16em,100%) minmax(5.5em,6em) minmax(5.5em,14em) minmax(5.5em,6em); }
  1331. .tbody_row_cell_details { white-space:pre; font-variant-numeric:tabular-nums; padding:3px 0; }
  1332. .tbody_row_cell_name_a { padding:3px 0; }
  1333. .tbody_row_cell_name_a::before { display:none; counter-increment:row; content:counter(row); width:36px; min-width:36px; height:14px; max-height:14px; min-height:14px; margin-top:2px; padding:0; float:left; text-align:right; font-variant-numeric:tabular-nums; }
  1334. .show_numbers #tbody td.name a::before { display:inline-block; }
  1335. .tbody_row_cell_name_a_span { display:flex; padding:2px 0; word-break:break-word; }
  1336. .tbody_row_cell_name_a_span input { margin:2px 6px 0 0; display:none; }
  1337. .tbody_row_cell_details.name { grid-column:1; text-align:left; }
  1338. .tbody_row_cell_details.size { grid-column:2; padding-right:12px; padding-left:0; text-align:right; }
  1339. .tbody_row_cell_details.date { grid-column:3; padding-right:12px; padding-left:0; white-space:normal; overflow:hidden; max-height:1em; overflow-wrap:break-word; text-align:right; }
  1340. .tbody_row_cell_details.date span { white-space:pre; display:inline-block; }
  1341. .tbody_row_cell_details.kind { grid-column: 4; text-align:right; padding-right:12px; }
  1342. .tbody_row_cell_details.kind::first-letter { text-transform:uppercase; }
  1343. .tbody_row_cell_details.ext { display:none; }
  1344. .tbody_row_cell_media_duration { display:none; padding:3px 12px 3px 0; font-variant-numeric:tabular-nums; }
  1345. tr.media, body.has_media tr.media #sorting_row_2 { grid-template-columns: minmax(24em,100%) minmax(4.5em,8em) minmax(5.5em,14em) minmax(5.5em,5.5em); }
  1346. tr.media .tbody_row_cell_name_a_span input { display:inline-block; }
  1347. body.has_media #tbody tr, body.has_media #sorting_row_2 { grid-template-columns: minmax(16em,100%) minmax(4em,6em) minmax(5.5em,6em) minmax(5.5em,14em) minmax(4em,6em); }
  1348. body.has_media .tbody_row_cell_media_duration { display:inline-block; grid-column: 2; text-align:right; }
  1349. body.has_media .tbody_row_cell_details.size { grid-column: 3; }
  1350. body.has_media .tbody_row_cell_details.date { grid-column: 4; }
  1351. body.has_media .tbody_row_cell_details.kind { grid-column: 5; text-align:right; padding-right:12px; }
  1352. body.has_media .tbody_row_cell_details.ext { display:none; }
  1353. body.has_media tr:not(.media) .tbody_row_cell_name { grid-column: 1 / span 2; }
  1354. body.has_media tr:not(.media) .tbody_row_cell_media_duration { display:none; }
  1355. #iframe_dir_list_wrapper tr:hover .tbody_row_cell_name_a, tr.selected .tbody_row_cell_name_a, tr.media[class*="_loaded"] .tbody_row_cell_name_a { font-weight:bold; opacity:1 !important; }
  1356. body.ignore_ignored_items tr.ignored { cursor:not-allowed; opacity:0.66; }
  1357. tr.invisible, tr.ignored, body.hide_ignored_items #tbody tr.ignored, body.hide_ignored_items #tbody tr.ignored, body.hide_ignored_items:not(.show_invisibles) #tbody tr.ignored.invisible, body.hide_ignored_items:not(.show_invisibles) #tbody tr.ignored.invisible, body:not(.show_invisibles) #tbody tr.invisible { display:none !important; }
  1358. body.show_invisibles tr.invisible, #tbody tr:not(.ignored):not(.invisible), body.show_invisibles #tbody tr.invisible, body.has_stats #tbody tr.invisible, body.has_stats #tbody tr.ignored, body.has_stats #tbody tr.ignored.hovered, body.hide_ignored_items.has_stats #tbody tr.ignored, body:not(.hide_ignored_items) #tbody tr.ignored:not(.invisible)
  1359. { display:grid !important; }
  1360. #iframe_body.is_blurred #dir_list { opacity:0.75; }
  1361. #iframe_body tr.is_blurred { background-color: rgba(172,202,235,0.66) !important; }
  1362. ${ CSS_UI_Icon_Rules() } /* background icons */
  1363. .has_icon_before::before, .has_icon_before_before { content:""; display:block; float:left; max-width:28px; width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; margin-top:4px; background-position:center; background-repeat:no-repeat; background-size:14px; }
  1364. #dir_list.sort_by_default #sort_by_default span::before, #dir_list.sort_by_name #sort_by_name span::before, #dir_list.sort_by_size #sort_by_size span::before, #dir_list.sort_by_date #sort_by_date span::before, #dir_list.sort_by_kind #sort_by_kind span::before, #dir_list.sort_by_ext #sort_by_ext span::before
  1365. { content:""; height:9px; background-image:${ SVG_UI_Icon("check_mark") }; background-repeat: no-repeat; background-size:10px; background-position: center; }
  1366. #dir_list.sort_by_default #sort_by_default span::after, #dir_list.sort_by_name #sort_by_name span::after, #dir_list.sort_by_size #sort_by_size span::after, #dir_list.sort_by_date #sort_by_date span::after, #dir_list.sort_by_kind #sort_by_kind span::after, #dir_list.sort_by_ext #sort_by_ext span::after, #parent a::before
  1367. { background-image:${ SVG_UI_Icon("chevron_up") }; background-repeat: no-repeat; background-size:10px; background-position: center; }
  1368. .sidebar #tbody tr { grid-template-columns: minmax(8em,100%) minmax(5.5em,6em) minmax(5.5em,6em) minmax(5.5em,6em); }
  1369. .has_subdirectory .dir_list_subdir { display:block; grid-column:1/ span 4; grid-row:3; width:100%; border:0; }
  1370. .tr[data-level] .tbody_row_cell_name_a::before { width:${ ( ( Number(getSearchParam("level") ) + 1) * 22 ) + 28 }px; }
  1371.  
  1372. body:not(.has_stats) #stats tbody, body.has_stats #stats_summary, body:not(.has_stats) #stats_summary_detailed_container, #stats .stats_kind span.file, #stats .stats_kind span.media, #total_duration
  1373. { display:none; }
  1374. #tfoot { margin-top:1px; }
  1375. #stats_container { display:block; }
  1376. #stats { font-weight:normal; font-size:0.875rem; cursor:pointer; overflow:hidden; width:100%; border-collapse:collapse; }
  1377. #stats tr { line-height:1.1; display:block; }
  1378. #stats td.tbody_row_cell_name { display:flex; grid-column:1 / span 3; font-variant-numeric:tabular-nums; }
  1379. #stats .tbody_row_cell_name_a { display:flex; -webkit-padding-start:0; padding:1px 0; }
  1380. #stats_summary tr, #stats_summary_detailed_total th, #stats_summary_playlist_container { padding:4px 8px; }
  1381. #stats_summary_totals, body.has_media #total_duration { display:inline-block; text-align:left; font-weight:normal; white-space:normal; padding-right:1em; }
  1382. #stats_summary_detailed_container { overflow-y:scroll; }
  1383. body.theme_dark #stats_summary_detailed_container { box-shadow: 0px -4px 4px 0px rgba(32,32,32,60); }
  1384. #stats .tbody_row_cell_name_a_span, #stats .stats_kind span { margin-right:0.5em; white-space:pre; }
  1385. #stats .stats_kind span::first-letter { text-transform:uppercase; }
  1386. #stats a::before { content:attr(data-count) !important; }
  1387. #stats a.tbody_row_cell_name_a:before {display:inline-block; }
  1388. #stats_details tr:hover, .summary_detailed:hover { font-weight:bold; }
  1389. body.has_media #total_duration::before { content:"Total Time: "; }
  1390. #stats .has_icon_before::before, #stats .has_icon_before_before { margin-top:-1px; max-width:28px; width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; display:block; float:left; }
  1391. #stats_summary_detailed_dirs .stats_kind::before { background-image:${ SVG_UI_File_Icon("file_icon_dir") }; }
  1392. #stats_summary_detailed_files .stats_kind::before { background-image:${ SVG_UI_File_Icon("file_icon_file_default") }; }
  1393. #stats_details tr.audio a span span::after { content:attr(data-audio_duration); white-space:pre; }
  1394. #stats_details tr.video a span span::after { content:attr(data-video_duration); white-space:pre; }
  1395. body.is_error #dir_list #tbody tr#is_error { display:block !important; grid:none !important; grid-template-columns:none !important; }
  1396. body.is_error #dir_list #tbody tr#is_error > td { padding:6px 8px; }
  1397. `;
  1398. // Gecko (Firefos) Styles:
  1399. const $gecko_style_rules = `
  1400. .dir::before { content:"" !important; display:none !important; }
  1401. body.is_gecko button { padding:revert; }
  1402. body.is_gecko #grid_btn .menu { top:-7px; left:-120px; }
  1403. body.is_gecko thead { font-size:100%; }
  1404. body.is_gecko #dir_list .dir::before { position:absolute; }
  1405. body.is_gecko #dir_list tr td.name span { display:-webkit-box; width:auto; white-space:normal; }
  1406. tr.dir td:not(:first-child), tr.file td:not(:first-child) { width:unset !important; }
  1407. body.is_gecko #dir_list tr td { min-width:calc(100% - 24px); }
  1408. body.is_gecko .dir::before { content:"" !important; display:none !important; }
  1409. body.is_gecko.use_default_icons:not(.is_converted_list) #dir_list .file .name .icon { padding-left:4px; background:none; }
  1410. body.is_gecko.use_default_icons #dir_list .file .name .icon img { margin-right:6px; height:14px; }
  1411. body.is_gecko #tbody > tr > td:not(:first-of-type) { float:left }
  1412. body.is_gecko #content_audio_title span { padding-top: 6px;, padding-bottom: 0; }
  1413. body.is_gecko #audio,body.is_gecko #audio_container { background-color: rgba(26,26,26,1); }
  1414. body.is_gecko #prev_track, body.is_gecko #next_track, body.is_gecko #close_audio { filter: invert(1); border:none !important; }
  1415. body.is_gecko #content_pane.has_zoom_image #content_image_container { display:block !important;}
  1416. `;
  1417. const $safari_style_rules = `
  1418. body.is_safari button { background-color: #FFF; }
  1419. body.is_safari.theme_dark #prev_track, body.is_safari.theme_dark #next_track, body.is_safari.theme_dark #close_audio { filter: invert(1); }
  1420. `;
  1421. const $chrome_style_rules = `
  1422. video::-webkit-media-controls-enclosure { border-radius:0 !important; }
  1423. `;
  1424. // ADD STYLES
  1425. function addStyles() {
  1426. $('head').find('style, link[rel="stylesheet"], link[href$="css"]').remove(); // remove any existing stylesheets
  1427. let default_styles = `
  1428. <style id="main_styles">${ $main_styles }</style>
  1429. <style id="conditional_styles">${ $conditional_styles }</style>
  1430. <style id="color_and_background_styles">${ $color_and_background_styles }</style>
  1431. <style id="font_styles"></style>
  1432. <style id="font_grid_styles"></style>
  1433. `;
  1434. if ( getBrowser() === 'is_gecko' ) { default_styles += `<style id="gecko_style_rules">${ $gecko_style_rules }</style>`; }
  1435. if ( getBrowser() === 'is_safari' ) { default_styles += `<style id="safari_style_rules">${ $safari_style_rules }</style>`; }
  1436. if ( getBrowser() === 'is_chrome' ) { default_styles += `<style id="chrome_style_rules">${ $chrome_style_rules }</style>`; }
  1437. $('head').append( default_styles );
  1438. }
  1439. // ***** END STYLES ***** //
  1440.  
  1441. // ***** INDEX PREP ***** //
  1442. // 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.
  1443. function getIndexType() {
  1444. let title = $('title').text() || '';
  1445. let index_el = document.querySelectorAll('body > ul, body > pre, body > table:last-of-type, body > div > table')[0];
  1446. let node_name = ( index_el !== undefined ? index_el.nodeName.toLowerCase() : 'body' ); // "body" is likely to be an error page
  1447. switch(true) {
  1448. case ( /Error|404|Not Found/i.test(title) ): node_name = 'error'; break;
  1449. case $protocol.startsWith('file'): if ( getBrowser() === 'is_gecko' ) { node_name = 'gecko'; } break;
  1450. }
  1451. let types = {'gecko':'gecko','ul':'list','pre':'pre','table':'table','th':'table','td':'table','div':'default','error':'error','body':'error','permission_denied':'permission_denied'};
  1452. let type = types[node_name];
  1453. return type;
  1454. }
  1455. // Return Index items, Index type, remove parent directory link, and add body class.
  1456. function getIndexItems() {
  1457. let type = getIndexType(), items;
  1458. switch(type) {
  1459. case 'gecko': items = $('body').find('> table > tbody'); break;
  1460. case 'list': items = $('body').find('> ul'); break;
  1461. case 'pre': items = $('body').find('> pre').html(); break;
  1462. case 'table': case 'td':
  1463. switch(true) {
  1464. case $('table > tbody').length === 1: items = $('body').find('table > tbody'); break;
  1465. case $('table > tbody').length < 1: items = $('body').find('table'); break; // tables without tbody
  1466. case $('table').length > 1: items = $('body').find('table').first(); break; // more than one table
  1467. }
  1468. items.find('tr th, tr td:contains(Parent Directory), a[href="../"], hr').parent('tr').remove();
  1469. break;
  1470. case 'default': items = $('body').find('> table').find('> tbody'); break;
  1471. case 'error': items = $('body').html(); break;
  1472. }
  1473. return [items,type];
  1474. }
  1475. // Index Prep: convert list type function
  1476. function convertGeckoType(items) {
  1477. let prepped_index = [];
  1478. const rows = Array.from(items.find('> tr'));
  1479. for ( let row of rows ) {
  1480. let prepped_row = [], cellContents = '', cells = Array.from( $(row).find('> td') ), link = ($(cells).find('a').attr('href'));
  1481. for ( let cell of cells ) {
  1482. cellContents = cell.innerText;
  1483. cellContents = ( cellContents !== undefined ? cellContents.trim() : '');
  1484. prepped_row.push(cellContents);
  1485. }
  1486. prepped_row[1] = prepped_row[1].replace(/\s*KB/,'000'); // convert reported size in KB to total bytes
  1487. prepped_row[2] = prepped_row[2] + ' '+ prepped_row[3];
  1488. prepped_row = prepped_row.slice(1,-1);
  1489. if ( link.length > 0 && link !== '/' && link !== '..' && link !== '../' && !/\?sort=|\?path=\&/.test(link) ) { prepped_row.unshift(link); } else { prepped_row = []; }// exclude some rows
  1490. if ( prepped_row.length > 0 ) { prepped_index.push(prepped_row); }
  1491. }
  1492. return prepped_index;
  1493. }
  1494. // Index Prep: convert list type function
  1495. function convertListType(items) {
  1496. let prepped_index = [];
  1497. const rows = items[0].children;
  1498. for ( let i = rows.length; i--; ) {
  1499. let row = rows[i];
  1500. if ( row.innerHTML.indexOf('Parent Directory') === -1 ) {
  1501. let prepped_row = [];
  1502. let link = $(row).find('a').attr('href');
  1503. row = row.innerHTML.replace(/<a .+?<\/a>\s*/,'');
  1504. let cells = row.split(' ');
  1505. for ( let cell of cells ) {
  1506. if ( cell.trim().length > 0 ) { prepped_row.push(cell); }
  1507. }
  1508. if ( link.length > 0 && link !== '/' && link !== '..' && link !== '../' && !/\?sort=|\?path=\&/.test(link) ) { prepped_row.unshift(link); } else { prepped_row = []; }// exclude some rows
  1509. if ( prepped_row.length > 0 ) { prepped_index.push(prepped_row); }
  1510. }
  1511. }
  1512. return prepped_index;
  1513. }
  1514. // Index Prep: convert pre type function
  1515. function convertPreType(items) {
  1516. let prepped_index = [];
  1517. const spaces = /\s{2,}/; // define regex for splitting rows
  1518. items = items.replace(/[ ]*<(hr|img)[^>]*>[ ]*|\&lt;dir\&gt;/gm,' ') // remove various elements: img, hr, br
  1519. .replace(/<br>/gi,'\n')
  1520. .replace(/[ ]*<h\d>[^<]*<\/h\d>[ ]*|[ ]*(<a[^>]+?>)(Parent|Parent Directory|Up|Root)(<\/a>.+?$|[ ]*<a[^>]+?>\s*<\/a>[ ]*|[ ]*<a href="(\.*\/"|\?[^>]*?)>[^<]*<\/a>[ ]*)/gmi,'') // 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)
  1521. .replace(/[ ]*(<a[^>]+?>)[^<]*(<\/a>)/g,'$1$2 ') // remove link text nodes
  1522. .replace(/(\w)<a /g,'$1 <a ')
  1523. ;
  1524. const rows = items.split('\n'); // create array of rows from items
  1525. for ( let i = rows.length; i--; ) {
  1526. let row = rows[i];
  1527. let prepped_row = [];
  1528. let cells = row.split(spaces); // assumes pre-type only uses two or more spaces between "columns"
  1529. let link;
  1530. for ( let j = cells.length; j--; ) {
  1531. let cell = cells[j];
  1532. if ( cell.trim().length > 0 ) {
  1533. if ( !cell.startsWith('<a ') ) { prepped_row.push(cell); } else { link = cell.split('"')[1]; } // extract link
  1534. }
  1535. }
  1536. if ( link !== undefined && link !== '..' && link !== '../' && link !== '/' && !/^\?|\?sort=|\?path=\&/mi.test(link) ) { prepped_row.unshift(link); } else { prepped_row = []; }// exclude some rows // exclude some
  1537. if ( prepped_row.length > 0 ) { prepped_index.push(prepped_row); } // add prepped row to index
  1538. }
  1539. return prepped_index;
  1540. }
  1541. // Index Prep: convert table type function
  1542. function convertTableType(type,items) { // for local chrome indexes and server-generated table-type indexes
  1543. let prepped_index = [], link, cell, cell_text, cell_html; // remove images, item counts, etc
  1544. const rows = ( items[0].children[0] === undefined ? '' : items[0].children[0].nodeName.toLowerCase === 'tbody' ? items[0].children[0].children : items[0].children );
  1545. for ( let i = rows.length; i--; ) {
  1546. let row = rows[i], prepped_row = [], cells = row.cells;
  1547. for ( let j = cells.length; j--; ) {
  1548. cell_text = cells[j].innerText; cell_html = cells[j].innerHTML;
  1549. switch(true) {
  1550. case cell_text.startsWith === '<img':
  1551. case cell_text.toLowerCase() === '<dir>':
  1552. case cell_text.toLowerCase() === 'directory':
  1553. case /^\s*\d+\s*item.*\s*$/gmi.test(cell_text): // "XX items"
  1554. break;
  1555. case cells[j].children[0] !== undefined && cells[j].children[0].nodeName.toLowerCase() === 'a': // get link
  1556. link = cells[j].children[0].attributes.href.value;
  1557. break;
  1558. case cell_html !== '&nbsp;': // get content from other cells
  1559. cell = cell_text.trim().replace(/(^[ ]*-[ ]*$|[ ]*-[ ]*\&nbsp;[ ]*$)/m,'—');
  1560. if ( cell.length > 0 ) { prepped_row.push( cell ); }
  1561. break;
  1562. }
  1563. }
  1564. if ( link !== undefined && link !== '..' && link !== '../' && link !== '/' && !/\?sort=|\?path=\&/.test(link) ) { prepped_row.unshift(link); } else { prepped_row = []; }// exclude some rows
  1565. if ( prepped_row.length > 1 ) { prepped_index.push(prepped_row); } // prepped_row.length > 2 in order to omit parent directory row
  1566. }
  1567. return prepped_index;
  1568. }
  1569. // Convert Error Type
  1570. function convertErrorType(items) {
  1571. $('body').addClass('is_error');
  1572. return items;
  1573. }
  1574. // Create Playlist items
  1575. function convertPlaylist(items) {
  1576. let prepped_index = []; let prepped_row = []; let rows, type;
  1577. 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
  1578. switch(true) { // determine playlist type;
  1579. case ( /#EXTINF:/.test(items) ): type = 'extm3u'; rows = items.split('#EXTINF:'); break; // rows made by splitting at "#EXTIMG:" prefix
  1580. default: type = 'm3u'; rows = items.split('\n'); break; // rows are just naked links
  1581. }
  1582. let rows_length = rows.length;
  1583. for ( let i = 0; i < rows_length; i++ ) {
  1584. let row = rows[i];
  1585. switch(true) { // get entry information: title, link, etc.
  1586. case type === 'extm3u': row = row.trim().split('\n'); prepped_row = row[1]; break; // split row into info ( = row[0] ) and link, but we're only using the link anyway....
  1587. case type === 'm3u': prepped_row = row; break; // m3u with urls only
  1588. }
  1589. if ( row.length > 1 ) { prepped_index.push([prepped_row]); }
  1590. }
  1591. return prepped_index;
  1592. }
  1593. // Index Prep: convert rows and return array of rows, with link, size, date-modified
  1594. function convertIndexItems(type,items) {
  1595. let converted = [];
  1596. switch(type) {
  1597. case 'gecko': converted = convertGeckoType(items); break;
  1598. case 'list': converted = convertListType(items); break;
  1599. case 'pre': converted = convertPreType(items); break;
  1600. case 'table': case 'default': converted = convertTableType(type,items); break;
  1601. case 'error': converted = convertErrorType(items); break;
  1602. }
  1603. return converted;
  1604. }
  1605. // INDEX PREP: Build new Index from prepped rows
  1606. function buildNewIndex(id,prepped_index,sort,type) {
  1607. let new_index_items = [], body_classes = new Set();
  1608. let new_row, 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;
  1609. 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;
  1610. let stats, stats_classes = [], stats_kinds = [];
  1611. let parent_id = ( getSearchParam('parent_id') || '' ), connector = ( getSearchParam('parent_id') ? '_' : '' ), level = ( Number(getSearchParam('level')) || 0 ); // ensure unique ids for subdirectory items
  1612. // add body classes according to index type
  1613. switch(type) {
  1614. case 'error': body_classes.add('is_error'); break;
  1615. case 'gecko': body_classes.add('is_converted_gecko'); break;
  1616. case 'list': body_classes.add('is_converted_list'); break;
  1617. case 'pre': body_classes.add('is_converted_pre'); break;
  1618. case 'table': case 'td': body_classes.add('is_converted_table'); break;
  1619. case 'default': body_classes.add('is_default'); break;
  1620. }
  1621. // create and format directory row
  1622. for ( let i = prepped_index_length; i--; ) {
  1623. item = prepped_index[i];
  1624. item_info = getLinkInfo(item[0]); // = [link,name,ext,kind,item_classes,body_classes];
  1625. item_link = item_info[0].trim();
  1626. item_name = item_info[1].replace(/^\s/m,'\&nbsp;').replace(/^\//m,'').replace(/([-_——])/g,'$1<wbr>'); // prep display name, with word breaks added after unbreakable chars
  1627. item_sort_name = escapeStr( item_info[1].toLocaleLowerCase() );
  1628. item_size_and_date = getItemSizeAndDate(item);
  1629. item_size = item_size_and_date[0];
  1630. item_sort_size = item_size_and_date[1];
  1631. item_date = item_size_and_date[2];
  1632. item_sort_date = item_size_and_date[3];
  1633. item_ext = item_info[2];
  1634. item_kind = item_info[3];
  1635. item_sort_kind = item_info[3];
  1636. item_classes = item_info[4];
  1637. item_disabled = ( item_classes.indexOf('local') > -1 || type === 'gecko' ? ' disabled="disabled"' : '' );
  1638. // Assemble row elements
  1639. name_span = `<span class="has_icon_before_before"></span><span class="tbody_row_cell_name_a_span"><input type="checkbox" tabindex="-1" checked="true" ${ item_disabled } autocomplete="off" /> ${ item_name }</span>`;
  1640. cell_link = `<a href="${ item_link }" class="icon tbody_row_cell_name_a text_color_111">${ name_span }</a>`;
  1641. cell_name = `<td class="tbody_row_cell tbody_row_cell_name name" data-name="${ item_sort_name.split("/")[0] }">${ cell_link }</td>`;
  1642. cell_time = `<td class="tbody_row_cell tbody_row_cell_media_duration" data-duration=""></span>`;
  1643. cell_size = `<td class="tbody_row_cell_details size details" data-size="${ item_sort_size }">${ item_size }</td>`;
  1644. cell_date = `<td class="tbody_row_cell_details date details" data-date="${ item_sort_date }">${ item_date }</td>`;
  1645. cell_kind = `<td class="tbody_row_cell_details kind details" data-kind="${ item_sort_kind }">${ item_kind }</td>`;
  1646. cell_ext = `<td class="tbody_row_cell_details ext details" data-ext="${ item_ext }"></td>`;
  1647. // Assemble rows
  1648. new_row = `<tr id="${ parent_id }${ connector }rowid-${ (prepped_index.length - i) }" class="${ item_classes }" data-kind="${ item_sort_kind }" data-ext="${ item_ext }" data-level="${ level }" style="padding-left:${ Number(level) * 22 }px;">${ cell_name } ${ cell_time } ${ cell_size } ${ cell_date } ${ cell_kind } ${ cell_ext }</tr>`;
  1649. new_index_items.push($(new_row)[0]);
  1650. //item_info[5].forEach(item_class => body_classes.add(item_class));
  1651. body_classes.add(item_info[5].join(' '));
  1652.  
  1653. // get media durations
  1654. getMediaDuration( item_link, item_sort_kind, function( duration ) { // function: do something with duration returned from callback
  1655. let item_sort_kind = document.getElementById( parent_id + connector +'rowid-'+ ( prepped_index.length - i) ).classList.value.match(/audio|video/)[0]; // not sure why this has to be defined again
  1656. if ( window.location.search.indexOf('subdirectory') > -1 && /audio|video/.test(item_sort_kind) ) { // get media durations for subdirectory items: send message from utility iframe to top
  1657. sendMessage('top','set_media_duration','', ['#'+ parent_id + connector +'rowid-'+ ( prepped_index.length - i), item_sort_kind, duration ] );
  1658. } else {
  1659. setMediaDuration('#'+ parent_id + connector +'rowid-'+ ( prepped_index.length - i), item_sort_kind, duration );
  1660. }
  1661. });
  1662. // build stats
  1663. stats_kinds.push(item_kind);
  1664. stats_classes.push(item_classes); // add item classes for stats
  1665. stats = buildStats(stats_classes,stats_kinds);
  1666. }
  1667. // Sort items
  1668. if ( sort === undefined ) { sort = getSearchParam('sort_by'); } // get sorting pref
  1669. let sort_direction = ( getSearchParam('sort_direction') === 'down' ? 'sort_direction_down' : 'sort_direction_up' ); // get sort direction
  1670. let sorted_index_items = sortDirList($(new_index_items), 'sort_by_'+ sort, sort_direction); // make initial sort
  1671. return [sorted_index_items, Array.from(body_classes).join(' '),stats];
  1672. }
  1673. // Get Media Durations (add to cue list, add to tr.audio
  1674. var getFormattedTime = (secs) => {
  1675. let sec_num = parseInt(secs, 10), hours = Math.floor(sec_num / 3600), minutes = Math.floor(sec_num / 60) % 60, seconds = sec_num % 60;
  1676. let formattedTime = [hours,minutes,seconds].map( v => v < 10 ? "0" + v : v ).filter( (v,i) => v !== "00" || i > 0 ).join(":");
  1677. formattedTime = formattedTime.replace(/^0/m,''); // remove initial 0
  1678. return formattedTime;
  1679. };
  1680. function getMediaDuration(link, kind, mediaInfoCallback) { // create temp element for each media link in order to get item duration, callback returns media
  1681. if ( !/audio|video/.test(kind) || link === undefined ) { return; } // abort if not a media file
  1682. let media = document.createElement(kind); // create audio or video element
  1683. media.src = link; // set the media element source
  1684. media.onloadedmetadata = function() { mediaInfoCallback( media.duration ); }; // return media_info = [duration, kind]
  1685. media.onerror = function() { mediaInfoCallback( 0 ); }; // media.onerror = function() { callback( new Error("File not found")); };
  1686. }
  1687. function setMediaDuration(id,kind,duration) { // Set Media Durations from getMediaDuration callback and subdirectory loading; set total duration
  1688. $(id).find('td.tbody_row_cell_media_duration').attr('data-duration',duration).text( getFormattedTime(duration) ); // add time to dir_list row details
  1689. let totalDurationEl = document.getElementById('total_duration'), total_duration;
  1690. if ( totalDurationEl !== null ) { // i.e., we are not setting stats in an iframe, so no total duration element...but this could change for content pane
  1691. total_duration = Number( totalDurationEl.getAttribute('data-total_duration') );
  1692. total_duration += Number(duration);
  1693. document.getElementById('total_duration').setAttribute('data-total_duration',total_duration);
  1694. document.getElementById('total_duration').innerText = getFormattedTime(total_duration);
  1695. }
  1696. if ( duration === 0 ) { $(id).addClass('disabled'); }
  1697. $('body').addClass('has_media');
  1698. }
  1699. // GET CONTENT SRC LINK, NAME, KIND, and EXT
  1700. function getLinkInfo(link) {
  1701. switch(true) {
  1702. case link === undefined: return; // return if link undefined
  1703. case link.startsWith('?') && link.indexOf('=') > 0: link = link.split('=')[1]; break; // php links
  1704. case link.startsWith('file://') && window.location.protocol === 'file:': link = link.split('file://')[1]; break; // local links
  1705. case link.startsWith('/') && window.location.protocol === 'file:': link = 'file://'+ link; break; // local links
  1706. case !link.startsWith('/') && !link.endsWith('/') && !/\./.test(link): link = '/'+ link +''; break;
  1707. }
  1708. if ( /\.php\?(path)=/.test(link) ) { link = link.split('=')[1]; } // attempt to deal with some php links
  1709. if ( /\/%3F[^\/]+\/*$/m.test(link) ) { link = link.replace(/\/%3F([^\/]+)\/*$/m,'/?$1'); } // fix query string for dir links; a rare situation, for links in filelists only?
  1710.  
  1711. let URL = newURL(decodeURIComponentSafe(encodeURIComponent(link)));
  1712. let prepped_link, display_name, kind, ext, item_classes = [], body_classes = [], aliases = new RegExp(/(symlink|alias|symbolic link)$/,'m');
  1713. switch(true) {
  1714. case $protocol !== 'file:': // non-local pages
  1715. switch(true) {
  1716. case URL.protocol === 'file:': case URL.protocol === undefined: prepped_link = link; item_classes.push('local','ignored'); break;
  1717. default: prepped_link = URL.href; // non-local pages
  1718. }
  1719. break;
  1720. default: // local pages;
  1721. switch(true) {
  1722. case URL.protocol !== 'file:': prepped_link = URL.href; break;
  1723. default: prepped_link = URL.pathname;
  1724. }
  1725. }
  1726. // prepare display name, body_classes, and item_classes
  1727. switch(true) {
  1728. case URL.pathname.endsWith('/'): // dirs and apps
  1729.  
  1730. display_name = URL.pathname.split('/').reverse()[1] + '/';
  1731. switch(true) {
  1732. case /\.app$|\.app\/$|\.exe$/m.test(display_name): // apps
  1733. ext = 'app'; kind = ext;
  1734. if ( $settings.apps_as_dirs === false ) { item_classes.sort().unshift('file','app'); } else { item_classes.sort().unshift('dir','app'); }
  1735. break;
  1736. default: kind = 'dir'; ext = 'dir'; // dirs
  1737. item_classes.unshift(kind);
  1738. }
  1739. if ( display_name.startsWith('.') ) { item_classes.push('invisible'); }
  1740. break;
  1741. default: // files
  1742. display_name = prepped_link.trim().split('/?')[0].split('/').reverse()[0];
  1743. switch(true) {
  1744. case display_name.toLowerCase().endsWith('symlink'): ext = 'symlink'; break;
  1745. case !/\./.test(display_name): ext = display_name.toLowerCase(); break; // if no '.' in link (typical for bin files), ...
  1746. default: // find the last . and get the remaining characters
  1747. ext = display_name.toLowerCase().slice(display_name.toLowerCase().lastIndexOf('.') + 1);
  1748. for ( let item_kind in $item_kind ) { if ( $item_kind[item_kind].includes( ext ) ) { kind = item_kind; } } // kind = types
  1749. if ( /url|url\/|webloc|webloc\//.test(ext) ) { kind = 'link'; } // links
  1750. switch(true) {
  1751. case kind === 'audio': item_classes.push('media'); body_classes.push('has_media','has_audio'); break;
  1752. case kind === 'video': item_classes.push('media'); body_classes.push('has_media','has_video'); break;
  1753. case kind === 'font' : body_classes.push('has_fonts'); break;
  1754. case kind === 'image': body_classes.push('has_images'); break;
  1755. }
  1756. if ( $row_settings.ignored.includes( ext ) ) { item_classes.push('ignored'); }
  1757. if ( display_name.startsWith('.') ) { item_classes.push('invisible'); }
  1758. }
  1759. if ( kind === undefined ) { kind = 'other'; }
  1760. item_classes = item_classes.sort(); item_classes.unshift(kind); item_classes.unshift('file');
  1761. }
  1762. if ( ext === undefined ) { ext = ''; }
  1763. if ( aliases.test(display_name) ) { item_classes.push('alias'); }
  1764. for ( let item_kind_system of $item_kind.system ) { if ( display_name.endsWith(item_kind_system) ) { item_classes.push('ignored'); } } // ignore various system items
  1765. item_classes = Array.from(new Set(item_classes)).join(' '); // remove dupe classes
  1766. return [decodeURIComponentSafe(encodeURIComponent(prepped_link)),decodeURIComponentSafe(display_name),ext,kind,item_classes,body_classes];
  1767. }
  1768. // Index Prep: get formatted row size and date
  1769. function getItemSizeAndDate(cells) {
  1770. let item_size_and_date = [], row_display_size, item_sort_size, row_display_date, item_sort_date, size_units = /[BYTES|B|K|KB|MB|GB|TB|PB|EB|ZB|YB]/;
  1771. if ( cells.length > 1 ) {
  1772. if ( cells[1].search(/[-:\/]/) !== -1 ) { // test for typical date/time separators.
  1773. row_display_date = cells[1]; row_display_size = cells[2];
  1774. } else {
  1775. row_display_date = cells[2]; row_display_size = cells[1];
  1776. }
  1777. }
  1778. // size
  1779. switch(true) {
  1780. case row_display_size !== undefined && row_display_size.toLowerCase() === 'dir':
  1781. case /undefined|—|-|,|\*/.test(row_display_size):
  1782. case row_display_size === '': // if size is undefined, empty, or punctuation
  1783. row_display_size = '&mdash;'; item_sort_size = '0'; // if no size supplied, use these defaults
  1784. break;
  1785. default:
  1786. item_sort_size = getItemSortSize(row_display_size);
  1787. switch(true) {
  1788. case !row_display_size.toUpperCase().match(size_units) : // if provided size is only numeric
  1789. row_display_size = formatBytes(row_display_size,1); // format byte size
  1790. break;
  1791. default:
  1792. 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
  1793. }
  1794. break;
  1795. break;
  1796. }
  1797. if ( row_display_size === 'NaN undefined' ) { row_display_size = '0 B'; }
  1798. // date
  1799. if ( [undefined,'','-'].includes(row_display_date) ) { row_display_date = '&mdash;'; item_sort_date = '0'; } else { item_sort_date = getItemDate(row_display_date); }
  1800. row_display_date = row_display_date.replace(/, (.+)/,'<span>, $1</span>'); // add spans for short date display
  1801. item_size_and_date.push( row_display_size, item_sort_size, row_display_date, item_sort_date );
  1802. return item_size_and_date;
  1803. }
  1804. // Index Prep: get row size for sorting
  1805. function getItemSortSize(val) {
  1806. let sort_size, values = val.replace(/(\d+)\s*([A-z]+)/,'$1 $2').split(' '), size = values[0], unit = values[1];
  1807. 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
  1808. if ( unit !== undefined ) { unit = unit.toUpperCase(); }
  1809. sort_size = size * factor[unit]; // convert byte size to multiplication factor
  1810. return sort_size;
  1811. }
  1812. // convert numeric sizes to display format
  1813. function formatBytes(val, decimals) {
  1814. 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));
  1815. if (val === 0) { return '0 Bytes'; } else { return parseFloat((val / Math.pow(k, i)).toFixed(dm)) +' '+ sizes[i]; }
  1816. }
  1817. // process date
  1818. function processDate(match,p1,p2,p3) { //date formats: 2017-10-09 13:12 || 2015-07-25T02:02:57.000Z || 12-Mon-2017 21:11
  1819. const mo = 'JanFebMarAprMayJunJulAugSepOctNovDec'.indexOf(p2)/3 + 1; // e.g., convert month into number, or use number
  1820. return p3 +'-'+ mo +'-'+ p1;
  1821. }
  1822. // Index Prep: get row date 2015-07-25T02:22:00.000Z
  1823. function getItemDate(val) {
  1824. let sort_date = val.replace(/^(\d{2})-(\w{3})-(\d{4})/m, processDate) // convert Month to number
  1825. .replace(/\b(\d{1})[-:/]/g,'0$1/') // add leading 0 for single digit numbers
  1826. .replace(/(\d{2})\/(\d{2})\/(\d{2}),/,'$3$1$2') // reorder MM/DD/YY dates
  1827. .replace(/-|:|\s+|\//g,''); // remove spacing characters
  1828. return sort_date;
  1829. }
  1830. // Stats
  1831. function buildStats(stats_classes,stats_kinds) { // durations = [total_duration,audio_duration,video_duration] from updateStats, subdirectories
  1832. stats_classes.sort();
  1833. let removed = [];
  1834. if ( /audio\s+audio_loaded/.test(stats_classes) ) { removed = stats_classes.splice(stats_classes.indexOf('audio audio_loaded'),1); } // remove some classes
  1835. let total = stats_classes.length, counts = [], kinds = [], stats_rows = [], total_dirs_invisible = 0, total_files_invisible = 0;
  1836. for ( let i = 0; i < total; i++ ) { // get key/value pairs for item_classes/total counts
  1837. counts[stats_classes[i]] = 1 + ( counts[stats_classes[i]] || 0 );
  1838. if ( /invisible|ignored/.test(stats_classes[i]) ) {
  1839. if ( /dir/.test(stats_classes[i]) ) { total_dirs_invisible++; }
  1840. if ( /file/.test(stats_classes[i]) ) { total_files_invisible++; }
  1841. }
  1842. }
  1843. 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
  1844. let total_dirs = ( kinds.dir || 0 ); // total directory count
  1845. let total_files = ( total - total_dirs ); // total file count
  1846. for ( let count in counts ) { // make detail row for each kind of item
  1847. let kinds_items = count.split(' '), stats_row_kinds = '';
  1848. kinds_items.forEach( item => ( stats_row_kinds += (`<span class="${ item }" >${ item }</span>`)) ); // create kind span
  1849. let stats_row = `<tr class="${ count }"><td class="tbody_row_cell tbody_row_cell_name name stats_count"><a class="icon tbody_row_cell_name_a text_color_111" data-count="${ counts[count] }"><span class="tbody_row_cell_name_a_span has_icon_before stats_kind">${ stats_row_kinds }</span></a></td></tr>`;
  1850. stats_rows.push(stats_row);
  1851. }
  1852. let stats = `<div id="stats_container"><table id="stats"><thead id="stats_summary" class="background_color_C0_40 text_color_111"> <tr><th id="stats_summary_totals" colspan="2">${ total } Items: ${ total_dirs } Directories, ${ total_files } Files</th><th id="total_duration" data-total_duration=""></th></tr></thead> <thead id="stats_summary_detailed_container" class="border_bottom background_color_C0_40 text_color_111"> <tr id="stats_summary_detailed_total" class="summary_detailed border_bottom"> <th colspan="2">${ total } Items (${ total_dirs_invisible + total_files_invisible } ignored or invisible)</th></tr> <tr id="stats_summary_detailed_dirs" class="dir summary_detailed background_color_D0_50"> <td class="tbody_row_cell tbody_row_cell_name name stats_count"><a class="icon tbody_row_cell_name_a text_color_111" data-count="${ total_dirs }"><span class="tbody_row_cell_name_a_span has_icon_before stats_kind">Dirs (${ total_dirs_invisible } ignored or invisible)</span></a></td></tr> <tr id="stats_summary_detailed_files" class="file summary_detailed background_color_D0_50"> <td class="tbody_row_cell tbody_row_cell_name name stats_count"><a class="icon tbody_row_cell_name_a text_color_111" data-count="${ total_files }"><span class="tbody_row_cell_name_a_span has_icon_before stats_kind">Files (${ total_files_invisible } ignored or invisible)</span></a></td></tr></thead> <tbody id="stats_details_container"> <tr><td><table id="stats_details"><tbody> ${ stats_rows.join('\n') } </tbody></td></tr> </tbody></table></div>`;
  1853. return stats;
  1854. }
  1855. function updateStats() { // called whenever media duration is returned...; kind and duration only supplied for subdirectories
  1856. let items = document.getElementById('tbody').getElementsByTagName('tr');
  1857. let stats_classes = [], stats_kinds = [], item_classlist = [];
  1858. let item_info;
  1859. //, audio_duration = ( $('#stats_details').find('span.audio').attr('data-audio_duration') || '' ), video_duration = ( $('#stats_details').find('span.video').attr('data-video_duration') || '' );
  1860. for ( let i = 0; i < items.length; i++ ) { // get classes and kind for each item
  1861. item_info = getLinkInfo( items[i].getElementsByClassName('tbody_row_cell_name_a')[0].getAttribute('href') ); // = [link,name,ext,kind,item_classes,body_classes];
  1862. item_classlist = item_info[4];
  1863. item_classlist = item_classlist.replace(/file|media|content_loaded|has_subdirectory|selected/g,'').trim(); // remove unwanted classes --> why file and media?
  1864. stats_classes.push(item_classlist);
  1865. stats_kinds.push(items[i].getAttribute('data-kind') );
  1866. }
  1867. $('#stats_container').remove(); // remove old stats
  1868. $('#tfoot').prepend( buildStats(stats_classes,stats_kinds) ); // build and prepend new stats element
  1869. updateDurations(true); // update after building stats
  1870. }
  1871. function updateDurations(bool) { // bool === true: don't bother updating stats details, since they aren't visible
  1872. let media_items = document.getElementById('tbody').querySelectorAll('tr.media');
  1873. if (media_items.length === 0 ) { $('body').removeClass('has_media has_audio has_video'); return; }
  1874. let kind, total_duration = 0, duration = 0, audio_duration = 0, video_duration = 0;
  1875. for ( let i = 0; i < media_items.length; i++ ) { // get classes and kind for each item
  1876. kind = media_items[i].getAttribute('data-kind'); // = [link,name,ext,kind,item_classes,body_classes];
  1877. duration = Number(media_items[i].getElementsByClassName('tbody_row_cell_media_duration')[0].getAttribute('data-duration'));
  1878. setMediaDuration(media_items[i].getAttribute('id'),kind,duration );
  1879. total_duration += duration;
  1880. document.getElementById('total_duration').setAttribute('data-total_duration',total_duration);
  1881. document.getElementById('total_duration').innerText = getFormattedTime(total_duration);
  1882. $('body').addClass('has_media');
  1883. switch(true) { // update stats details
  1884. case bool === true: break;
  1885. case kind === 'audio': $('body').addClass('has_audio');
  1886. audio_duration += Number(duration);
  1887. document.getElementById('stats_details').querySelectorAll('span.audio')[0].setAttribute('data-audio_duration',' (Total Time: '+ getFormattedTime(audio_duration) +')');
  1888. break;
  1889. case kind === 'video': $('body').addClass('has_video');
  1890. video_duration += Number(duration);
  1891. document.getElementById('stats_details').querySelectorAll('span.video')[0].setAttribute('data-video_duration',' (Total Time: '+ getFormattedTime(video_duration) +')');
  1892. break;
  1893. }
  1894. }
  1895. }
  1896. // END INDEX PREP
  1897. // ***** MAKE NEW INDEX ***** //
  1898. function makeNewIndex(el,sort) {
  1899. const index_items = getIndexItems(el), items = index_items[0], type = index_items[1];
  1900. const converted_index = convertIndexItems( type, items ); // = array of rows: ["link","date","size"]
  1901. switch(type) {
  1902. case 'error': return [['<tr id="is_error"><td>'+ items +'</td></tr>'],'is_error','']; break;
  1903. default: { let new_index = buildNewIndex( $(el).attr('id'), converted_index, sort, type ); return [new_index]; }
  1904. }
  1905. }
  1906. // ***** END DIR_LIST SETUP ***** //
  1907. const $body = $('body');
  1908.  
  1909. // ***** UI SETUP ***** //
  1910. function prepDocHead() {
  1911. document.title = 'Index of: '+ current_location; // change the doc title to current location
  1912. // $('head').prepend('<meta charset="utf-8"><base href="'+ window.location.origin +'">');// .find('#title').removeAttr('id');
  1913. $('head').find('script').remove(); // remove any existing scripts
  1914. if ( window.location.protocol.startsWith('file') ) { // add custom favicon for local directories
  1915. $('head').prepend('<link href="data:image/png;base64,' + SVG_UI_File_Icon('favicon') +'" rel="icon" sizes="16x16" />');
  1916. }
  1917. addStyles(); // add css
  1918. }
  1919. // UI Setup: build UI and add body classes and other initial settings
  1920. function getUIPrefBodyClasses() {
  1921. let queries = new URLSearchParams(window.location.search).entries(); // make new search params from window.location.search
  1922. queries = Object.fromEntries(queries);
  1923. let body_classes = [], settings = Object.assign({},queries,$settings); // merge $settings and query settings
  1924. for ( let key in settings ) {
  1925. switch(true) {
  1926. case ['bookmarks','grid_font_size','grid_image_size','UI_font','UI_font_size'].includes(key):
  1927. break; // ignore these keys (values are set in css or by setUpTextEditorUI)
  1928. case key === 'enable_text_editing':
  1929. if ( getSearchParam(key) === 'true') { body_classes.push(key); } else { body_classes.push('disable_text_editing'); }
  1930. break;
  1931. case ['default_text_view'].includes(key):
  1932. body_classes.push( getSearchParam(key) );
  1933. break;
  1934. case key === 'editor_theme':
  1935. switch(true) {
  1936. case getSearchParam('editor_theme') === 'default': body_classes.push( 'editor_theme_default' ); break;
  1937. // case getSearchParam('editor_theme') === 'default': body_classes.push( 'editor_theme_'+ getSearchParam('theme') );
  1938. case getSearchParam('editor_theme') === 'light': body_classes.push( 'editor_theme_light' ); break;
  1939. case getSearchParam('editor_theme') === 'dark': body_classes.push( 'editor_theme_dark' ); break;
  1940. }
  1941. break;
  1942. case ['theme','sort_by','sort_direction'].includes(key): // non-boolean prefs: add key_value to body classes
  1943. body_classes.push( key +'_'+ getSearchParam(key) );
  1944. break;
  1945. case getSearchParam(key) === 'true': // booleans: only add the key to body classes
  1946. body_classes.push(key);
  1947. break;
  1948. }
  1949. }
  1950. body_classes.push(getBrowser()); body_classes.push('is_'+getOS()); // add browser and os classes
  1951. return body_classes.join(' ');
  1952. }
  1953. // Build UI: Append all assembled elements to $body
  1954. function buildUI() {
  1955. switch(true) {
  1956. case window.self === window.top: { // if it's not an iframe...
  1957. const make_new_index = makeNewIndex('body'); // make index
  1958. const new_index = make_new_index[0][0];
  1959. const body_classes = make_new_index[0][1] +' '+ getUIPrefBodyClasses();
  1960. $main_content.find('#tbody').append(new_index); // append index to #main_content el
  1961. $main_content.find('#tfoot').prepend(make_new_index[0][2]); // append stats
  1962. prepDocHead(); // add styles, title, favicon, meta tags
  1963. $('body').empty().removeClass(); // remove all existing content
  1964. $('body').addClass(body_classes).attr('id','top').attr('lang','en').append($main_content); // add body classes, id, lang attr, and append the main content
  1965. initMedia(); // initialize media
  1966. autoLoadFile(); // autoload media and cover art, if any
  1967. break;
  1968. }
  1969. case window.self !== window.top && !window.location.pathname.endsWith('.pdf'):
  1970. setUpIframeUI();
  1971. break;
  1972. }
  1973. }
  1974. buildUI();
  1975. // SET UI TO DEFAULT SETTINGS: remove queries;
  1976. function defaultSettings() {
  1977. let query_str = '';
  1978. if ( getSearchParam('selected') !== undefined ) { query_str += 'selected='+ getSearchParam('selected'); }
  1979. if ( getSearchParam('history') !== undefined ) { query_str += 'history='+ getSearchParam('history'); }
  1980. if ( query_str.length > 0 ) { query_str = '?' + query_str.replace(/\s/g,'+'); }
  1981. window.location.assign(current_location + query_str);
  1982. }
  1983. $('#default_settings').on('click', function(e) {
  1984. e.preventDefault(); e.stopPropagation();
  1985. $('body').removeClass('has_menu');
  1986. if (window.confirm( 'Are you sure you want to remove all your temporary UI settings from the URL query string?' ) ) { defaultSettings(); }
  1987. });
  1988. // SAVE USER SETTINGS
  1989. function saveSettings(file_name, data) { saveFile(data,'application/json',file_name); }
  1990. $('#export_settings').on('click',function(e) {
  1991. e.preventDefault(); e.stopPropagation();
  1992. $('body').removeClass('has_menu');
  1993. const settings_string = ( JSON.stringify($settings,null,'\t'));
  1994. saveSettings('settings.json',settings_string);
  1995. });
  1996. // Show/Close Help
  1997. function showHelp() { $('body#top').addClass('has_help'); }
  1998. $('#show_help').on('click','span', function(e) { e.stopPropagation(); showHelp(); });
  1999. $('#close_help').on('click', function(e) { e.preventDefault(); $('body').removeClass('has_help'); });
  2000. $('#help_container').on('click', function(e) { e.stopPropagation(); });
  2001. // Click Menu Bookmark (with warning)
  2002. function setLocation(link) { window.location = link; }
  2003. $('#parent_dir_nav, #parents_dir_nav + .menu, #menu .bookmark').on('click','a', function(e) {
  2004. e.preventDefault();
  2005. switch(true) {
  2006. case $(this).attr('href').indexOf('file://') > -1 && $protocol !== 'file:':
  2007. $body.addClass('has_warning').find('#warnings_container').addClass('warning_local_bookmark'); $('#warning_btn_ok').focus(); break;
  2008. case $body.hasClass('has_playlist'): case $body.hasClass('has_filelist'): closePlaylist(); break;
  2009. default: showWarning( 'setLocation', $(this).attr('href') );
  2010. }
  2011. });
  2012. // Show Menus
  2013. function showMenus(el) {
  2014. sendMessage('iframe','top_has_menu');
  2015. $('body').removeClass('hide_sidebar');
  2016. let position = $(el).position(), id = $(el).attr('id');
  2017. if ( $('body').hasClass('has_stats') ) { $('#stats').click(); }
  2018. $(el).find('> ul').css({'top':position.top + $(el).innerHeight() + 1 + 'px'});
  2019. switch(id) {
  2020. case 'parents_dir_menu':
  2021. switch(true) {
  2022. case $('body').hasClass('has_menu_parents'): $('body').removeClass('has_menu_parents'); break;
  2023. default: $('body').addClass('has_menu_parents').removeClass('has_menu');
  2024. }
  2025. break;
  2026. case 'menu_container':
  2027. switch(true) {
  2028. case $('body').hasClass('has_menu'): $('body').removeClass('has_menu'); sendMessage('iframe','top_closed_menu'); break;
  2029. default: $('body').addClass('has_menu').removeClass('has_menu_parents'); sendMessage('iframe','top_has_menu');
  2030. }
  2031. }
  2032. }
  2033. $('#parents_dir_menu, #menu_container').on('click',function(e) { e.stopPropagation(); showMenus($(this)); });
  2034. // Click menu
  2035. function clickMenu() {
  2036. $('#menu').find('.selected:not(.hovered)').removeClass('selected').find('a,> span,label').click();
  2037. $('#menu').find('.hovered').removeClass('selected hovered');
  2038. $('body').removeClass('has_menu');
  2039. if ( $('body').hasClass('focus_content') ) { sendMessage('iframe','close_menu'); $('#content_iframe').focus(); }
  2040. if ( $body.hasClass('focus_content') ) { focusContent(); }
  2041. }
  2042. // Close menus
  2043. function closeMenus() {
  2044. switch(true) {
  2045. case !$('body').hasClass('has_menu') && !$('body').hasClass('has_menu_parents') && !$('body').hasClass('has_stats'): break;
  2046. default:
  2047. $('body').removeClass('has_menu has_menu_parents faded has_stats has_help');
  2048. $('#menu').find('.selected,.hovered').removeClass('selected hovered');
  2049. }
  2050. }
  2051. // Show Stats
  2052. function showStats() {
  2053. updateDurations();
  2054. $('body').addClass('has_stats').removeClass('has_menu has_menu_parents');
  2055. $('#stats_details').css({'height':$('#stats_container').height() - $('#stats_summary_detailed_container').height() - 4 });
  2056. }
  2057. $('#tfoot').on('click','#stats_summary', function(e) { e.stopPropagation(); showStats(); });
  2058. $(document).on('click', function(e) { e.stopPropagation(); closeMenus(); });
  2059.  
  2060. // Toggle UI Preferences: after clicking .toggle_UI_pref items (menu, buttons, etc.), update searchParams
  2061. function toggleUIpref(pref_ID) {
  2062. let message_target = ( window.self === window.top ? 'iframe' : 'top' ), sorted, sort_direction;
  2063. let bodyClassNames = ( message_target === 'iframe' ? document.getElementById('top').className.split(' ') : null );
  2064. if ( pref_ID === 'theme' && $('body').hasClass('theme_dark') ) { pref_ID = 'theme_light'; }
  2065. if ( pref_ID === 'theme' && $('body').hasClass('theme_light') ) { pref_ID = 'theme_dark'; }
  2066. switch(true) {
  2067. // Text editor prefs
  2068. case pref_ID === 'editor_theme_default':
  2069. pref_ID = 'editor_'+ bodyClassNames.find( el => el.startsWith('theme') ); // no break
  2070. /* falls through */
  2071. case ( /editor_theme|editor_theme_light|editor_theme_dark/.test(pref_ID) ):
  2072. case ( /split_view|source_text|toggle_source|toggle_preview|toggle_html|preview_text|preview_html/.test(pref_ID) ):
  2073. toggleTextEditorPrefs(pref_ID); // Text Editor Preferences
  2074. if ( window.top === window.self ) { sendMessage('iframe',pref_ID); }
  2075. break;
  2076. // Enable/Disable Text editing
  2077. case pref_ID === 'enable_text_editing': // needs to be here and not in switch above -- why?
  2078. $('body').toggleClass('enable_text_editing disable_text_editing');
  2079. $('.selected.text, .selected.markdown, .selected.code').find('a').click();
  2080. break;
  2081. // Sorting
  2082. case ( /sort_by_name|sort_by_default|sort_by_time|sort_by_size|sort_by_date|sort_by_kind|sort_by_ext/.test(pref_ID) ): // toggle sorting
  2083. closeSubdirectory(); // subdirectory sorting not supported
  2084. switch(true) {
  2085. case $('body').hasClass(pref_ID): // pref_ID = current sort, reverse sort direction
  2086. sort_direction = document.getElementsByTagName('body')[0].classList.value.match(/sort_direction_\w+/)[0];
  2087. sort_direction = ( sort_direction === 'sort_direction_up' ? 'sort_direction_down' : 'sort_direction_up' );
  2088. $('body').removeClass('sort_direction_up sort_direction_down').addClass(sort_direction);
  2089. toggleSearchParam( sort_direction );
  2090. break;
  2091. case !$('body').hasClass(pref_ID):
  2092. sort_direction = 'sort_direction_'+ $settings.sort_direction;
  2093. $('body').removeClass('has_menu sort_by_default sort_by_name sort_by_time sort_by_size sort_by_date sort_by_kind sort_by_ext').addClass(pref_ID);
  2094. $('body').removeClass('sort_direction_up sort_direction_down').addClass(sort_direction);
  2095. break;
  2096. }
  2097. sorted = sortDirList( $('#tbody').find('tr'), pref_ID, sort_direction ); // make initial sort
  2098. $('#tbody').empty().append(sorted);
  2099. scrollThis('tbody','selected',false); // true = instant scroll
  2100. // if ( $('body').hasClass('sort_by_default') ) { $('#tbody').find('#tbody tr.dir.border_top,#tbody tr.dir.border_bottom').toggleClass('border_top border_bottom'); }
  2101. switch(true) { // sort grids --> change this to actual sort, not reload
  2102. case $content_pane.hasClass('has_font_grid'): $('#show_font_grid').click(); break;
  2103. case $content_pane.hasClass('has_image_grid'): $('#show_image_grid').click(); break;
  2104. case $content_pane.attr('data-content') === 'has_grid': $('#grid_btn').click(); break;
  2105. }
  2106. break;
  2107. // various UI prefs
  2108. case ( /alternate_background|hide_ignored_items|ignore_ignored_items|show_invisibles|show_numbers|hide_sidebar|play_all_media|show_details/.test(pref_ID) ): // toggle other boolean UI settings
  2109. $('body').toggleClass(pref_ID);
  2110. if ( pref_ID === 'show_details' ) { $('#show_details').blur(); $('body').focus(); }
  2111. if ( window.top === window.self ) { sendMessage('iframe',pref_ID); }
  2112. break;
  2113. // toggle main UI theme
  2114. case ( /theme_light|theme_dark/.test(pref_ID) ):
  2115. $('body').removeClass('theme_dark theme_light').addClass(pref_ID);
  2116. if ( $('body').hasClass('editor_theme_default') ) { $('body').removeClass('editor_theme_light editor_theme_dark').addClass('editor_'+ pref_ID); } else { toggleUIpref('editor_theme'); }
  2117. if (window.top === window.self) { sendMessage(message_target,pref_ID); }
  2118. break;
  2119. }
  2120. toggleSearchParam(pref_ID); // nobreak
  2121. if ( window.top === window.self ) {
  2122. if ( $('.selected').length) { scrollThis('tbody','selected',false); } // true = instant scroll
  2123. }
  2124. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  2125. }
  2126. // set Text Editor Preferences (from menus or toolbar buttons)
  2127. function toggleTextEditorPrefs(pref_ID) {
  2128. let message_target = ( window.self === window.top ? 'iframe' : 'top' );
  2129. switch(true) {
  2130. case ( /editor_theme|editor_theme_default|editor_theme_light|editor_theme_dark/.test(pref_ID) ): // toggle text editor theme
  2131. switch(true) {
  2132. case pref_ID === 'editor_theme': $('body').toggleClass('editor_theme_light editor_theme_dark');
  2133. pref_ID = ( $('body').hasClass('editor_theme_light') ? 'editor_theme_light' : 'editor_theme_dark' );
  2134. break;
  2135. case pref_ID === 'editor_theme_default':
  2136. pref_ID = ( $('body').hasClass('editor_theme_light') ? 'editor_theme_light' : 'editor_theme_dark' ); // arg sent to iframe to set editor theme
  2137. $('body').removeClass('editor_theme_light editor_theme_dark').addClass('editor_theme_default');// editor_theme_'+ getSearchParam('theme') );
  2138. break;
  2139. case pref_ID === 'editor_theme_light': $('body').removeClass('editor_theme_default editor_theme_dark').addClass('editor_theme_light'); break;
  2140. case pref_ID === 'editor_theme_dark': $('body').removeClass('editor_theme_default editor_theme_light').addClass('editor_theme_dark'); break;
  2141. }
  2142. break;
  2143. case pref_ID === 'source_text':
  2144. switch(true) {
  2145. case $('body').hasClass(getSearchParam('default_text_view')) : // if split view, remove split, show source
  2146. $('body').removeClass(getSearchParam('default_text_view')).addClass('source_text');
  2147. break;
  2148. default: // show split, show default text view
  2149. $('body').addClass(getSearchParam('default_text_view') +' source_text ');
  2150. }
  2151. $('#toggle_split_view').click();
  2152. break;
  2153. case ( /toggle_preview|toggle_html|preview_text|preview_html/.test(pref_ID) ): // toggle default text editor view
  2154. if ( !$('body').hasClass(pref_ID) ) {
  2155. $('body').removeClass('source_text preview_text preview_html').addClass(pref_ID);
  2156. }
  2157. break;
  2158. case ( /split_view/.test(pref_ID) ): // toggle other boolean UI settings
  2159. $('body').toggleClass(pref_ID);
  2160. }
  2161. switch(true) { //pref_ID += '_'+ getSearchParam('editor_theme');
  2162. // case pref_ID === 'editor_theme': sendMessage('top','toggle_text_editor_theme','',pref_ID); break;
  2163. // case ( /editor_theme_default|editor_theme_light|editor_theme_dark/.test(pref_ID) ): sendMessage('iframe','text_editor_toolbar_button','',pref_ID); break;
  2164. default: sendMessage(message_target,'text_editor_toolbar_button','',pref_ID);
  2165. }
  2166. }
  2167. // Click Toggle UI Pref elements
  2168. function toggleUIPrefOnClick(e,el) {
  2169. e.stopPropagation();
  2170. if ( !$(el).is('input') ) { e.preventDefault(); } // allow checkboxes to be checked
  2171. if ( !$(el).hasClass('disabled') ) { toggleUIpref( $(el).attr('data-ui_pref') ); }
  2172. closeMenus();
  2173. }
  2174. $('.toggle_UI_pref').on('click',function(e) { toggleUIPrefOnClick(e,this); });
  2175. $('#content_text').on('click','.toggle_UI_pref',function(e) { toggleUIPrefOnClick(e,this);}); // text editing UI is not in DOM on page load; why doesn't $('body').on('click','.toggle_UI_pref'... work?
  2176. // RESIZE Sidebar/Content Pane
  2177. function resizeSidebar(f) {
  2178. f.stopPropagation();
  2179. let $sidebar_wrapper = $('#sidebar_wrapper'), startX = f.pageX, window_width = window.innerWidth, sidebar_width = $sidebar_wrapper.width();
  2180. $('body').addClass('has_overlay'); // prevent interference from the rest of ui
  2181. $(document).on('mousemove',function(e) {
  2182. e.stopPropagation(); e.preventDefault();
  2183. let deltaX = e.pageX - startX;
  2184. if ( e.pageX > 230 && e.pageX < window_width - 200 ) {
  2185. $sidebar_wrapper.css({'width':sidebar_width + deltaX + 'px'});
  2186. $content_pane.css({'width':(window_width - sidebar_width) - deltaX + 'px'});
  2187. }
  2188. // scrollThis('tbody','selected',false); // true = instant scroll
  2189. });
  2190. $(document).on('mouseup',function() {
  2191. $('body').removeClass('has_overlay'); // remove the overlay
  2192. $(document).off('mousemove'); // remove eventlistener
  2193. setSearchParam('width',$sidebar_wrapper.width()); // set the sidebar width query
  2194. if ( $body.hasClass('focus_content') ) { focusContent(); } // refocus content if necessary
  2195. });
  2196. }
  2197. $('#handle').on('mousedown', function(f) { f.stopPropagation(); resizeSidebar(f); });
  2198.  
  2199. // ***** BASIC UI FUNCTIONS ***** //
  2200.  
  2201. // Scroll Selected Items
  2202. function scrollThis(container_ID, scroll_el_class, bool) {
  2203. let $container = document.getElementById(container_ID);
  2204. if ( $container.height === 0 ) { return; } // don't scroll hidden elements
  2205. let $scroll_el = ( scroll_el_class !== undefined ? $container.getElementsByClassName(scroll_el_class) : null );
  2206. let scroll_behavior = ( ( bool !== undefined || bool === true || $content_pane.attr('data-content') === 'has_grid' ) ? 'instant' : 'smooth' ); // instant allows sidebar & grid to scroll simultaneously
  2207. let scroll_block = ( $('body').hasClass('is_gecko') ? 'start' : 'nearest' );
  2208. if ( $scroll_el !== undefined && $scroll_el.length === 1 ) {
  2209. $scroll_el[0].scrollIntoView({ behavior:scroll_behavior, block:scroll_block, inline:'nearest' });
  2210. }
  2211. }
  2212. // ***** SORTING ***** //
  2213. function sortIndex(els,sort_type,sort_direction) { // sort_id = sort type
  2214. let sort_id = sort_type.split('_').reverse()[0];
  2215. const new_sort = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
  2216. let sorted = [], aName, bName, aData, bData;//, aLevel, bLevel, aKind, bKind;
  2217. sorted = els.removeClass('sorted border_top').sort((a, b) => {
  2218. // aLevel = $(a).attr('data-level'); bLevel = $(b).attr('data-level'); // subdirectory level
  2219. aName = $(a).find('td.name').attr('data-name'); bName = $(b).find('td.name').attr('data-name'); // aName, bName = item name
  2220. aData = $(a).find('td[data-'+ sort_id +']').attr('data-'+ sort_id); bData = $(b).find('td[data-'+ sort_id +']').attr('data-'+ sort_id); // aData, bData = size, date, kind, ext, time
  2221. switch(true) {
  2222. case sort_direction === 'sort_direction_down':
  2223. switch(true) {
  2224. case new_sort.compare(aData, bData) === 0: return new_sort.compare(aName, bName); // if same data kind, sort by name
  2225. default: return new_sort.compare(aData, bData); // otherwise sort by data kind
  2226. }
  2227. case sort_direction === 'sort_direction_up': // reverse sort
  2228. switch(true) {
  2229. case new_sort.compare(bData, aData) === 0: return new_sort.compare(bName, aName);
  2230. default: return new_sort.compare(bData, aData);
  2231. }
  2232. }
  2233. });
  2234. switch(true) { // add sorted border style
  2235. case ( /default/.test(sort_id) ): // dirs and files
  2236. break;
  2237. case ( /kind|ext/.test(sort_id) ): // different item kinds or extensions
  2238. sorted = sorted.sort((a, b) => {
  2239. if ( $(a).find('td[data-'+ sort_id +']').attr('data-'+ sort_id) !== $(b).find('td[data-'+ sort_id +']').attr('data-'+ sort_id) ) { $(a).addClass('sorted border_top'); }
  2240. });
  2241. break;
  2242. case ( /time/.test(sort_id) ): // if sorting by time, add rule between dirs and files
  2243. sorted = sorted.sort((a, b) => { if ( $(a).attr('data-kind') !== $(b).attr('data-kind') ) { $(a).addClass('sorted border_top'); } });
  2244. break;
  2245. }
  2246. return sorted;
  2247. }
  2248. // Sort the Dir List on click
  2249. function sortDirList(rows, sort_type, sort_direction) {
  2250. let $sorted = [], $sort_all = rows, $sort_dirs = $sort_all.filter('.dir:not(.app)'), $sort_files = $sort_all.filter('.file,.app');// === 'default' ? 'name' : id;
  2251. switch(true) {
  2252. case sort_type === 'sort_by_default' || ( sort_type !== 'sort_by_name' && $settings.dirs_on_top === true ):
  2253. const $sorted_dirs = sortIndex($sort_dirs, sort_type, sort_direction);
  2254. const $sorted_files = sortIndex( $sort_files, sort_type, sort_direction );
  2255. if (sort_direction === 'sort_direction_down') { $sorted = $.merge($sorted_dirs,$sorted_files); } else { $sorted = $.merge($sorted_files,$sorted_dirs); }
  2256. break;
  2257. default:
  2258. $sorted = sortIndex( $sort_all, sort_type, sort_direction );
  2259. }
  2260. return $sorted;
  2261. }
  2262. // ***** END SORTING ***** //
  2263. // ***** END BASIC UI FUNCTIONS ***** //
  2264.  
  2265. // ***** CONTENT PANE ***** //
  2266. // Focus Sidebar
  2267. function focusSidebar() {
  2268. document.activeElement.blur();
  2269. closeMenus();
  2270. $('body').removeClass('faded focus_content').find('#sidebar').focus();
  2271. sendMessage('iframe','blur_iframe');
  2272. }
  2273. // Focus content
  2274. function focusContent(id,e) {
  2275. let activeElementID = document.activeElement.id;
  2276. document.activeElement.blur();
  2277. closeMenus();
  2278. $('body').addClass('focus_content');
  2279. switch(true) {
  2280. case id === 'content_iframe' && cmdKey(e) && e.key === 'ArrowDown' && window.self === window.top && $('body').hasClass('focus_content'):
  2281. $content_iframe.focus();
  2282. sendMessage('iframe','iframe_arrow_navigation','','ArrowDown');
  2283. $('#iframe_body a').click();
  2284. break;
  2285. case $content_pane.attr('data-content') === 'has_font': $('#specimen').focus(); break;
  2286. case activeElementID.toLowerCase() === 'text_preview' && getFocusableEls('#text_preview').length > 0: getFocusableEls('#text_preview').first().focus(); break;
  2287. case $content_pane.attr('data-content') === 'has_text_editor': focusTextEditorPanes(); break; // focus text editor
  2288. case ( /has_text|has_markdown|has_code|has_htm|has_dir|has_app/.test($content_pane.attr('data-content') ) ): // no break; focus iframe text editor
  2289. case $content_iframe.hasClass('has_content'): // focus iframe and/or iframe text editor
  2290. $('#content_iframe').focus(); if ( e !== undefined && e.shiftKey ) { sendMessage('iframe','shift_focus_iframe'); } else { sendMessage('iframe','focus_iframe'); } break;
  2291. case ( /has_video|has_pdf/.test($content_pane.attr('data-content') ) ): // no break; video, pdf
  2292. case $content_pane.attr('data-content') === undefined: // no break; data-content undefined
  2293. case $('body').hasClass('has_playlist'): $('body').removeClass('focus_content'); break; // don't focus content in this and the above two cases
  2294. case $content_pane.attr('data-content') === 'has_grid': $('#content_grid').focus(); break; // grids
  2295. case $content_pane.attr('data-content') === 'has_image': $('#content_image').focus(); break; // images
  2296. case id !== undefined: document.getElementById(id).focus(); break;
  2297. }
  2298. }
  2299. // get focusable elements
  2300. function getFocusableEls(id) {
  2301. let focusableEls = ( id !== undefined ? $(id).find('a,button,input,select,textarea,div[contenteditable]').filter(':visible') : $('a,button,input,select,textarea,div[contenteditable]').filter(':visible') );
  2302. return focusableEls;
  2303. }
  2304. // focus focusable elements in iframe files or text editor preview
  2305. function focusFocusableEls(e,id) {
  2306. e.preventDefault();
  2307. let els = Array.from(getFocusableEls(id));
  2308. switch(true) {
  2309. case e.shiftKey: // tab + shift: focus prev
  2310. switch(true) {
  2311. case els.indexOf(document.activeElement) === 0: sendMessage('top','focus_sidebar'); break;
  2312. case !els.includes(document.activeElement): els[els.length - 1].focus(); break; // if nothing focused...focus last el
  2313. default: els[els.indexOf(document.activeElement) - 1].focus(); // else focus previous
  2314. }
  2315. break;
  2316. case document.activeElement === els[els.length - 1]: sendMessage('top','focus_sidebar'); break; // if last focussable el, focus sidebar
  2317. default: els[els.indexOf(document.activeElement) + 1].focus(); // tab: select next focusable element
  2318. }
  2319. }
  2320. // focus button (for warnings)
  2321. function focusButton(id) { let el = document.getElementById(id); el.classList.add('focus'); el.focus(); }
  2322. //***** SHOW INDIVIDUAL CONTENT TYPES *****//
  2323. // Show Grid
  2324. function showGrid(id) {
  2325. if ( id !== undefined ) { makeGrids(id); } // initial make grid items; otherwise, just unhide existing grid (see below)
  2326. closeContent();
  2327. const selected_ID = $('#tbody').find('.selected.image, .selected.font').attr('id');
  2328. $('#grid_btn').addClass('has_grid');
  2329. $('#content_pane').removeClass('has_hidden_grid').attr('data-content','has_grid').find('div[data-id="'+ selected_ID +'"]').addClass('selected').siblings().removeClass('selected hovered');
  2330. focusContent();
  2331. }
  2332. // Hide Grid
  2333. function hideGrid() { if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_pane.removeAttr('data-content').addClass('has_hidden_grid'); } }
  2334. // Show Hidden Grid
  2335. function showHiddenGrid() { if ( $content_pane.hasClass('has_hidden_grid') ) { showGrid(); } }
  2336. // Close Grid (remove grid elements, etc.)
  2337. function closeGrid() {
  2338. $('#grid_btn').removeClass('has_grid');
  2339. if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_pane.removeAttr('data-content'); }
  2340. $content_pane.removeClass('has_image_grid has_font_grid').find('#content_grid').attr('style','').empty();
  2341. }
  2342. // Show Text Editor
  2343. function showTextEditor() {
  2344. switch(true) {
  2345. case $content_pane.attr('data-content') === 'has_text_editor': // hide open text editor
  2346. hideTextEditor(true);
  2347. break;
  2348. case !$body.hasClass('has_text_editor_UI'): // add the text editor UI if loading text editor for first time
  2349. setUpTextEditorUI();
  2350. default: // show editor
  2351. // closeContent();
  2352. $('#content_video').trigger('pause');
  2353. $content_pane.removeClass('has_hidden_text_editor').attr('data-content','has_text_editor'); // empty title
  2354. $('#text_editor_row').addClass('has_text_editor');
  2355. setContentTitle();
  2356. focusContent();
  2357. }
  2358. }
  2359. // Hide Text Editor
  2360. function hideTextEditor(bool) {
  2361. if ( $content_pane.attr('data-content') === 'has_text_editor' ) {
  2362. $content_pane.removeAttr('data-content');
  2363. if ( bool === true ) { $content_pane.addClass('has_hidden_text_editor'); }
  2364. if ( $('tr.selected').length > 0 ) { clickRow($('tr.selected').attr('id')); }
  2365. focusSidebar();
  2366. }
  2367. }
  2368. // Show Hidden Text Editor
  2369. function showHiddenTextEditor() { if ( $content_pane.hasClass('has_hidden_text_editor') ) { showTextEditor(); } }
  2370. // showIndexSource();
  2371.  
  2372. // Show Audio
  2373. function showAudio(id,iframe_audio_link,bool) {
  2374. closeMedia('video'); document.activeElement.blur(); $('body').focus();
  2375. let row, link, title;
  2376. switch(true) {
  2377. case id === 'content_iframe_file': // clicked iframe audio files
  2378. link = decodeURIComponentSafe(iframe_audio_link);
  2379. title = link.slice(link.lastIndexOf('/') + 1);
  2380. $content_pane.addClass('has_audio has_iframe_audio');
  2381. $('#content_iframe_utility').attr('src',link.slice(0,link.lastIndexOf('.') )+ '.cue' ); // load cue sheet in utility iframe
  2382.  
  2383. break;
  2384. default: // dir_list audio files
  2385. row = getElById(id);
  2386. switch(true) {
  2387. case row.hasClass('local'): break;
  2388. case bool === 'true': // bool !== undefined: if from autoLoadFile, just select file (don't add .audio_loaded class)
  2389. row.addClass('selected');
  2390. if ( $('.dir.content_loaded').length === 1 ) { $('.dir.content_loaded').addClass('selected').siblings().removeClass('selected'); } // select dir.selected instead of media
  2391. break;
  2392. default: row.addClass('audio_loaded selected').siblings().removeClass('audio_loaded selected'); // otherwise select loaded media
  2393. }
  2394. link = row.find('a').attr('href');
  2395. title = row.find('.name').find('.tbody_row_cell_name_a_span').text();
  2396. $content_pane.addClass('has_audio').removeClass('has_iframe_audio');
  2397. getCueSheet(id,link,'audio');
  2398. }
  2399. $audio_player.attr('src', link );
  2400. $('#content_audio_title').find('span').empty().text( title );
  2401. $('#content_audio_playlist').removeClass('has_content');
  2402. //setPlayerStatus('play');
  2403. }
  2404. // Show Video
  2405. function showVideo(id,link) { closeMedia('audio'); getCueSheet(id,link); document.activeElement.blur(); $content_video.focus(); } // rest of processing is done in showContent();
  2406. // GET CUE SHEET
  2407. function getCueSheet(id,link,kind) { // id = 'content_iframe_file' or selected dir_list media id, link = selected href, kind = audio or video
  2408. $('.cue_sheet_track_list_container').removeClass('has_cue_sheet').empty();
  2409. let media_file_name = getElById(id).find('.name').attr('data-name');
  2410. media_file_name = (media_file_name !== undefined ? media_file_name.slice(0,media_file_name.lastIndexOf('.')) + '.cue' : null); // undefined === from iframe, can't search for cuesheet
  2411. let $cue_files;
  2412. if ( $('#tbody tr[data-ext="cue"]').length === 0 ) { return; } else { $cue_files = $('#tbody tr[data-ext="cue"]'); }// abort if no cue files found
  2413. let cue_file = $cue_files.filter( function(i) { return $cue_files[i].cells[0].dataset.name === media_file_name; }); // find matching cue sheet
  2414. if ( cue_file.length === 1 ) {
  2415. $(cue_file[0]).addClass('content_loaded');
  2416. $('#content_iframe_utility').attr('src',$(cue_file[0]).find('a').attr('href').trim() ); // load cue sheet in utility iframe
  2417. }
  2418. switch(kind) { // get durations
  2419. case 'audio': $('#cue_sheet_track_list_container_audio').attr('data-duration',document.getElementById('audio').duration); break;
  2420. case 'video': $('#cue_sheet_track_list_container_video').attr('data-duration',document.getElementById('content_video').duration); break;
  2421. }
  2422. }
  2423. // Process Cue Sheet
  2424. function processCueSheet(cue_sheet_text) {
  2425. let cue_sheet_tracks, track, command, prepped_track_list = [], track_id, display_time, index, index_sec;
  2426. cue_sheet_text = cue_sheet_text.replace(/\t/g,' ');
  2427. if ( !cue_sheet_text.startsWith('TRACK') ) {
  2428. // let cue_sheet_info = cue_sheet_text.slice(0,cue_sheet_text.indexOf('TRACK ')); // if there is a file info header, remove it;
  2429. cue_sheet_tracks = cue_sheet_text.slice(cue_sheet_text.indexOf('TRACK ')).split('TRACK ').reverse();
  2430. } else {
  2431. cue_sheet_tracks = cue_sheet_text.split('TRACK ').reverse();
  2432. }
  2433. for ( track of cue_sheet_tracks ) { // for each track in the cue sheet...
  2434. let prepped_track = [];
  2435. track = track.trim().split(/[\n\r]/);
  2436. track_id = track.shift().split(' ')[0];
  2437. for ( command of track ) { // for each command (e.g.: PERFORMER "Artist"), trim and split it into command and value
  2438. command = command.replace(/\"$/m,'').trim();
  2439. switch(true) {
  2440. case ( /^PERFORMER\s+/.test(command) ): prepped_track[1] = '<span class="cue_performer">'+ command.replace(/^PERFORMER\s+\&quot;/,'') +'</span>'; break; // artist name
  2441. case ( /^TITLE\s+/.test(command) ): prepped_track[2] = '<span class="cue_title">'+ command.replace(/^TITLE\s+\&quot;/,'') +'</span>'; break; // track title
  2442. case ( /^INDEX\s+01\s+/.test(command) ): // time index
  2443. display_time = command.replace(/INDEX\s+\d+\s+/,'');
  2444. index = display_time.split(':').reverse(); // N.B.: cue time format is mm:ss:ff (ff = frames @ 75 frames/sec)
  2445. index_sec = index[0]/75 + index[1]*1 + index[2]*60; // convert index to seconds
  2446. prepped_track[3] = '<span class="cue_index">'+ display_time +'</span></li>';
  2447. break;
  2448. }
  2449. }
  2450. prepped_track[0] = '<li id="track_'+ track_id +'" class="cue_sheet_track background_color_D0_50" data-duration="'+ index_sec +'">' +
  2451. '<span class="cue_track_id has_background_before">'+ track_id +'</span>';
  2452. if ( prepped_track[1] === undefined || ( /cue_performer"><\//.test(prepped_track[1]) ) ) { prepped_track[1] = '<span class="cue_performer">[missing]</span>'; }
  2453. if ( prepped_track[2] === undefined || ( /cue_title"><\//.test(prepped_track[2]) ) ) { prepped_track[2] = '<span class="cue_title">[missing]</span>'; }
  2454. if ( prepped_track.length > 3 ) { prepped_track_list.push(prepped_track.join('')); } // prepped_track.length > 3 to prevent adding empty track
  2455. }
  2456. prepped_track_list = `<ul class="cue_sheet_track_list border_bottom"><li id="cue_sheet_background" class="background_color_D0_50"></li>
  2457. <li id="cue_sheet_title" class="cue_sheet_track header background_color_D0_50 border_top">
  2458. <span>${ getLinkInfo( $('#content_iframe_utility').attr("src") )[1] }</span>
  2459. </li>
  2460. <li class="cue_sheet_track header background_color_D0_50 border_top border_bottom">
  2461. <span class="cue_track_id">Track</span><span class="cue_performer">Artist</span>
  2462. <span class="cue_title">Title</span>
  2463. <span class="cue_index" title="mm:ss:ff">Time</span>
  2464. </li>`+ prepped_track_list.reverse().join('') +'</ul>';
  2465. switch(true) {
  2466. case $('tr.audio_loaded').length > 0: case $content_pane.hasClass('has_iframe_audio'):
  2467. $('#cue_sheet_track_list_container_audio').addClass('has_cue_sheet').append(prepped_track_list); // add cue sheet track list to menu
  2468. break;
  2469. case $('tr.video.content_loaded').length > 0: case $content_pane.hasClass('has_iframe_file'):
  2470. $('#cue_sheet_track_list_container_video').addClass('has_cue_sheet').prepend(prepped_track_list); // add cue sheet track list to menu
  2471. break;
  2472. }
  2473. }
  2474. // Set track time on click
  2475. $('.cue_sheet_track_list_container').on('click','li:not(.header)',function() {
  2476. let $parent_nav_id = $(this).closest('nav').attr('id');
  2477. switch(true) {
  2478. case $(this).hasClass('selected'): playPauseMedia(); break;
  2479. default:
  2480. $(this).addClass('selected').siblings().removeClass('selected');
  2481. let time = $(this).attr('data-duration'); // cue time format is mm:ss:ff (ff = frames, 75 frames/sec)
  2482. switch($parent_nav_id) {
  2483. case 'cue_sheet_track_list_container_audio': if ( time < document.getElementById('audio').duration ) { $('#audio').prop('currentTime',time); } break;
  2484. case 'cue_sheet_track_list_container_video': if ( time < document.getElementById('content_video').duration ) { $('#content_video').prop('currentTime',time); } break;
  2485. }
  2486. }
  2487. });
  2488. $('.cue_sheet_track_list_container').on('mouseenter',function() { // add selected class to first track whose time is less than currentTime
  2489. if ( $(this).attr('id') === 'cue_sheet_track_list_container_video' ) { $(this).find('.cue_sheet_track_list').css({'top':$(this).height() }); }
  2490. let currentTime = document.getElementById('audio').currentTime;
  2491. $(this).find('.cue_sheet_track_list').attr('data-duration',currentTime).css({'height':$('#content_container').outerHeight() + $('#content_title').outerHeight() });
  2492. let currentTrack = $(this).find('.cue_sheet_track_list').find('li').filter(function() { return parseInt( $(this).attr('data-duration')) <= Math.round(currentTime); }).last();
  2493. currentTrack.addClass('selected').siblings().removeClass('selected');
  2494. });
  2495. // SHOW FONT (and create font items) row
  2496. function setContentFontSource(id,bool,font_grid,link) { // bool = true if for show font grid, id from fontGridItems(); link = from previewed directory
  2497. let font_styles = $('#font_styles');
  2498. const font_family = ( link !== undefined ? link.slice(link.lastIndexOf('/') + 1,link.lastIndexOf('.')) : getElById(id).find('.name').attr('data-name') );
  2499. const font_url = ( link !== undefined ? link : getElById(id).find('a').attr('href') );
  2500. switch(true) {
  2501. case bool === false: // just show selected font
  2502. if ( font_styles.html().length > 0 ) { font_styles.empty(); } // delete previous @font-face rule
  2503. font_styles.append( `@font-face { font-family: "${ font_family }"; src: url("${ font_url }"); }` );
  2504. $content_font.css({ 'font-family':'"'+ font_family +'"' }); // set content font style
  2505. break;
  2506. case font_grid === 'font_grid': { // else make font grid items
  2507. const $font_grid_item_el = $( `<div class="font_grid_item border_top_x background_color_EE_22" data-id="${ id }"></div>` );
  2508. let $grid_item = $font_grid_item_el.clone();
  2509. const display_name = font_family;
  2510. $('#font_grid_styles').append( `@font-face { font-family: "${ font_family }"; src: url("${ font_url }"); }`);
  2511. $grid_item.append( `<p class="text_color_111">${ display_name.toUpperCase() }</p><h2 style=\'font-family: "${ font_family }"\'; ><a class="text_color_111" href="${ font_url }">${ display_name.slice(0,font_family.lastIndexOf(".")) }</a></h2>` );
  2512. return $grid_item;
  2513. }
  2514. }
  2515. }
  2516. // Open Font File (req: opentype.js font parsing)
  2517. $('#open_font_label').on('click',function(e) { e.stopPropagation(); });
  2518. // Open font
  2519. $('#menu').on('change','#open_font',function(e) { $('body').removeClass('has_menu faded'); openFile(e,'font'); });
  2520. // Open Font File
  2521. function openFontFile(files,reader) {
  2522. closeContent();
  2523. hideGrid();
  2524. $content_pane.attr('data-content','has_font_file');
  2525. $content_font.addClass('has_content');
  2526. parseFont(reader.result);
  2527. $('#title span').html( files.name );
  2528. $('#open_font').val(''); // reset input to allow same font to be reopened immediately after closing.
  2529. focusContent();
  2530. }
  2531. // Parse font (req opentype.js)
  2532. function parseFont(fontblob) {
  2533. let font = window.opentype.parse(fontblob);
  2534. getFontInfo(font);
  2535. let $glyphs_container = $('#glyphs_container');
  2536. $glyphs_container.empty();
  2537. let $glyph_container_el = $('<div class="glyph_container background_color_EE_22 border_right_x border_bottom_x"></div>');
  2538. let $glyph_canvas_el = $('<canvas class="glyph invert" width="120" height="120"></canvas>');
  2539. let $glyph_info_el = $('<div class="glyph_info text_color_333"></div>');
  2540. let $glyph_viewer = $('#glyph_viewer');
  2541. $glyph_viewer.data('data-font-name',font.names.fullName.en);
  2542. let glyphs = font.glyphs;
  2543. $content_font.data('data-glyphs',glyphs); // add glyphs data to $content_font
  2544. // Draw glyphs
  2545. let glyph, glyph_width, context_X, bounding_box, glyph_unicode, $glyph_container, this_glyph, context, $glyph_info;
  2546. for ( let i = 0; i < glyphs.length; i++ ) {
  2547. glyph = glyphs.glyphs[i];
  2548. // Glyph width
  2549. bounding_box = glyph.getBoundingBox();
  2550. glyph_width = bounding_box.x2 - bounding_box.x1;
  2551. context_X = (60 - glyph_width/24);
  2552. // Add glyph info and append elements
  2553. glyph_unicode = ( glyph.unicode !== undefined ? '#'+ glyph.unicode : glyph.unicode );
  2554. $glyph_container = $glyph_container_el.clone();
  2555. $glyph_info = $glyph_info_el.clone();
  2556. $glyph_info.text(glyph.index +': '+ glyph.name +', '+ glyph_unicode);
  2557. $glyph_container.attr('id','glyph_container_'+ glyph.index ).attr('data-id','glyph_container_'+ glyph.index);
  2558. $glyph_container.append( $glyph_canvas_el.clone().attr('id','glyph_'+ glyph.index ) ).append($glyph_info);
  2559. $glyphs_container.append( $glyph_container );
  2560. // Draw glyph
  2561. this_glyph = document.getElementById('glyph_'+ glyph.index);
  2562. $(this_glyph).data('contextX',context_X);
  2563. context = this_glyph.getContext('2d');
  2564. glyph.draw(context, context_X, 84, 72);
  2565. }
  2566. }
  2567. // Get font info
  2568. function getFontInfo(font) {
  2569. let font_names = font.names;
  2570. let $font_info = $('<table id="font_info" class="background_color_C0_40"><thead><tr><th colspan=2 class="border_top text_color_111">FONT INFO: '+ font.names.fullName.en.toUpperCase() +'</th></tr></thead><tbody id="font_info_body" class="background_color_D0_50 border_top"></tbody></table>');
  2571. for ( let name in font_names ) {
  2572. let value = font_names[name].en;
  2573. if ( name.endsWith('URL') ) {
  2574. let href = value;
  2575. if ( !value.startsWith('http') ) { href = 'http://'+ value; } // in case url without protocol is used
  2576. value = '<a class="text_color_111" href="'+ href +'" target="_blank">'+ value +'</a>';
  2577. }
  2578. $font_info.find('tbody')
  2579. .append('<tr><td class="font_info_name border_right border_bottom text_color_111">'+ name +': </td><td class="font_info_value border_bottom text_color_111">'+ value +'</td></tr>');
  2580. }
  2581. let num_glyphs = font.numGlyphs; // glyph count
  2582. $font_info.find('tbody')
  2583. .append('<tr><td class="font_info_name border_bottom border_right text_color_111">numGlyphs: </td><td class="font_info_value border_bottom text_color_111">'+ num_glyphs +'</td></tr>');
  2584. $content_font.find('#font_viewer').prepend($font_info);
  2585. }
  2586. // Show glyph viewer
  2587. $content_font.on('click','.glyph',function(e) {
  2588. e.stopPropagation();
  2589. let id = $(this).attr('id');
  2590. $(this).parent('div').addClass('selected').siblings().removeClass('selected');
  2591. showFontGlyph(id);
  2592. });
  2593. function showFontGlyph(id) {
  2594. let $glyph_viewer = $('#glyph_viewer');
  2595. let glyphX = $('#'+ id).data('contextX');
  2596. id = id.slice(id.lastIndexOf('_') + 1); // convert id to number only
  2597. let glyphs = $content_font.data('data-glyphs'); // get glyphs
  2598. let glyph = glyphs.get(id);
  2599. let glyph_name = glyph.name;
  2600. let glyph_path = glyph.getPath(glyphX,84,72);
  2601. let glyph_SVG = glyph_path.toSVG().replace(/"/g,'\'');
  2602. // set the background SVG image:
  2603. let SVG_prefix = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' x=\'0px\' y=\'0px\' width=\'120px\' height=\'120px\' viewBox=\'0 0 120 120\' style=\'enable-background:new 0 0 120 120;\' xml:space=\'preserve\' ><g>';
  2604. $glyph_viewer.show().css({'background-image': SVG_prefix + glyph_SVG +'</g></svg>")'});
  2605. $glyph_viewer.data('data-raw-svg',glyph_SVG).data('data-glyph-name',glyph_name).find('#glyph_viewer_info div').text(id +': '+ glyph_name +', #'+ glyph.unicode ); // for saving SVG
  2606. $content_pane.attr('data-content','has_glyph');
  2607. }
  2608. // Save glyph svg
  2609. function saveGlyph() {
  2610. let file_name = $('#glyph_viewer').data('data-font-name') +'—'+ $('#glyph_viewer').data('data-glyph-name') +'.svg';
  2611. 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" width="120px" height="120px" viewBox="0 0 120 120" style="enable-background:new 0 0 120 120;" xml:space="preserve" ><g>';
  2612. let data_suffix = '</g></svg>';
  2613. let data = data_prefix + $('#glyph_viewer').data('data-raw-svg') + data_suffix;
  2614. saveFile(data,'image/svg+xml',file_name);
  2615. }
  2616. $content_font.on('click','#save_svg',function(e) { e.stopPropagation(); saveGlyph(); });
  2617. // END OPEN FONT FILE Operations
  2618.  
  2619. // Create new #content_pdf
  2620. function setupContentPDF() {
  2621. let $embed = '<embed id="content_pdf" class="content" name="plugin" tabindex="0" data-kind="pdf"></embed>';
  2622. $('#content_pdf').remove();
  2623. $content_image_container.after($embed);
  2624. }
  2625. // OPEN LINK FILES (webloc, url)
  2626. function openLink(ext) {
  2627. let link, content = ( $protocol === 'file:' ? $('#iframe_body pre').text() : document.getElementsByTagName('iframe')[0].contentWindow.document.children[0].children[1].innerHTML );
  2628. switch(true) {
  2629. case content === undefined: break;
  2630. case ( /webloc|inetloc/.test(ext) ): link = content.split('<string>')[1].split('</string>')[0]; break;
  2631. case ext === 'url': link = content.split('URL=')[1].split('\n')[0];
  2632. }
  2633. let url = ( link.startsWith('file') ? link : newURL(link) );
  2634. switch(true) {
  2635. case url === undefined: break;
  2636. case link.startsWith('file') && window.location.protocol !== 'file:': // local files and links
  2637. $body.addClass('has_warning').find('#warnings_container').addClass('warning_local_file'); $('#warning_btn_ok').focus();
  2638. break;
  2639. case link.startsWith('file') && window.location.protocol === 'file:': // local files and links
  2640. case url.origin === window.location.origin: // same origin
  2641. sendMessage('top','show_content','',[link,getLinkInfo(link)[3]]);
  2642. break;
  2643. default:
  2644. window.open(link,'_blank'); // open non-local links in new tab
  2645. }
  2646. }
  2647. // END SHOW INDIVIDUAL CONTENT TYPES
  2648.  
  2649. // ***** MAIN SHOW CONTENT FUNCTIONS ***** //
  2650. // Set Content Pane classes
  2651. function setContentClasses(id, kind, content_el) {
  2652. switch(true) { // add loaded or playing classes
  2653. case kind === 'audio': // case kind === 'video':
  2654. getElById(id).addClass('selected').siblings('.audio').removeClass('audio_loaded selected');
  2655. break;
  2656. default: // non-media items
  2657. getElById(id).addClass('content_loaded').siblings().removeClass('content_loaded');
  2658. }
  2659. switch(true) {
  2660. case kind === 'audio':
  2661. if ( id === 'content_iframe_file' ) { $content_pane.addClass('has_iframe_audio'); } else { $content_pane.addClass('has_audio'); }
  2662. break;
  2663. case ( /app|dir/.test(kind) ): // apps and directories
  2664. if ( id === 'content_iframe_file' ) { $content_pane.attr('data-content','has_dir').addClass('has_iframe_dir'); }
  2665. else { $content_pane.attr('data-content','has_dir').addClass('has_dir').removeClass('has_iframe_dir has_iframe_file'); }
  2666. break;
  2667. default:
  2668. if ( id === 'content_iframe_file' ) { $content_pane.removeClass('has_dir').addClass('has_iframe_file').attr('data-content','has_'+ kind);}
  2669. else { $content_pane.attr('data-content','has_'+ kind).removeClass('has_dir has_iframe_dir has_iframe_file'); }
  2670. }
  2671. $('#content_'+ content_el).addClass('has_content');
  2672. }
  2673. // add body classes
  2674. // function addBodyClasses(params) {
  2675. // for ( let param of params ) {
  2676. // switch(true) {
  2677. // case getSearchParam(param) === '': case getSearchParam(param) === undefined: break; // do nothing if query undefined
  2678. // case getSearchParam(param) === 'true': document.getElementsByTagName('body')[0].classList.add(param); break; // else add the query key if true
  2679. // default: document.getElementsByTagName('body')[0].classList.add(getSearchParam(param)); // or add the query value
  2680. // }
  2681. // }
  2682. // }
  2683. // LINKS, SEARCH PARAMS, AND QUERIES
  2684. // get query string for various content kinds
  2685. function makeSearchParams(params) {
  2686. let query_str = new URLSearchParams();
  2687. for ( let param of params ) { query_str.append(param,getSearchParam(param)); }
  2688. return query_str;
  2689. }
  2690. function getLinkQueries(kind) {
  2691. let query_str, params;
  2692. switch(true) {
  2693. case kind === 'pdf': // pdfs
  2694. query_str = '#view=fitB&scrollbar=1&toolbar=1&navpanes=1';
  2695. break;
  2696. case ( /text|markdown|code/.test(kind) ): // editable text files
  2697. params = ['split_view','default_text_view','theme','sync_scroll'];
  2698. query_str = '?'+ makeSearchParams(params).toString() +"&editor_theme="+ ( getSearchParam('editor_theme') === 'default' ? getSearchParam('theme') : getSearchParam('editor_theme') === 'light' ? 'light' : 'dark') + "&enable_text_editing="+ ( $('body').hasClass('enable_text_editing') ? 'true' : 'false' );
  2699. break;
  2700. case ( /app|dir/.test(kind) ): // apps and directories
  2701. params = ['sort_by','sort_direction','show_numbers','use_custom_icons','show_invisibles','hide_ignored_items','ignore_ignored_items','alternate_background','theme'];
  2702. query_str = '?'+ makeSearchParams(params).toString();
  2703. }
  2704. return query_str;
  2705. }
  2706. // CREATE LINKS FOR VARIOUS CONTENT KINDS
  2707. function setContentSources(id, kind, link, content_el) {
  2708. switch(true) { // make source link for content elements; default is link
  2709. case kind === 'font': setContentFontSource(id,false,'',link); break;
  2710. }
  2711. $('#content_'+ content_el).attr('src',link); // set source for content element
  2712. }
  2713. // SET CONTENT TITLE
  2714. function setContentTitle(link) { // kind = dir or files
  2715. let $title = $('#title span'), title_text = '';
  2716. let $selected = $('#tbody tr.selected'),selected_link, selected_title, content_link;//, content_title;
  2717. selected_link = ( $selected.length === 1 ? decodeURIComponentSafe($selected.find('a').attr('href')).trim() : '' ); // selected sidebar item; link = href
  2718. selected_title = ( $selected.length === 1 ? $selected.find('.tbody_row_cell_name_a_span').text() : '' ); // title from stored data
  2719. 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
  2720. content_link = ( content_link_info !== undefined ? decodeURIComponentSafe(content_link_info[0]).trim() : '' );
  2721. // content_title = ( content_link_info !== undefined ? content_link_info[1] : '' );
  2722. switch(true) {
  2723. case selected_link !== content_link: title_text = content_link; break; // unsynced iframe items
  2724. default: title_text = selected_title; // synced iframe items
  2725. }
  2726. title_text = title_text.split('/').join('/<wbr>').split('_').join('_<wbr>'); // allow nice line breaks in title
  2727. $title.empty().html(title_text); // set title text
  2728. }
  2729. // Get Image Dimensions
  2730. function getImageDimensions(link, callback) { if ( link !== undefined ) { let img = new Image(); img.src = link; img.onload = function() { callback( this.width, this.height ); }; } }
  2731. function setImageDimensions() {
  2732. switch(true) {
  2733. case window.top !== window.self: break; // ignore iframe content
  2734. case $content_pane.attr('data-content') === 'has_image':
  2735. getImageDimensions( $content_image.attr('src'), function( width,height ) {
  2736. $('#title span').attr('data-after',' ('+ width +'px × '+ height +'px) ('+ ( ($content_image.width()/width)*100 ).toFixed(1) +'%)'); // callback
  2737. });
  2738. break;
  2739. default: $('#title span').removeAttr('data-after'); // remove image dimensions
  2740. }
  2741. }
  2742. // SHOW IFRAME CONTENT called when iframe loads
  2743. function showIframeContent(args) {
  2744. let link = $content_iframe.attr('data-content_name'), kind = $content_iframe.attr('data-kind');
  2745. switch(true) { // make source link for content elements; default is link
  2746. case ( /app|dir/.test(kind) ): $content_pane.attr('data-content','has_dir'); break;
  2747. default: $content_pane.attr('data-content','has_'+kind);
  2748. }
  2749. if ( args[0].indexOf('subdirectory=true') < 0 ) { $content_pane.attr('data-loaded','loaded'); setContentTitle(link,kind); } // show iframe, hide loading spinner, set title
  2750. }
  2751. // SHOW OTHER CONTENT
  2752. function showContent(id, args) { // show any content on row click; last two arguments are only from clicking iframe links; args = [link,kind]
  2753. let row, link_info, link, kind;
  2754. switch(true) { // set vars and content_iframe attributes for iframe content or sidebar content
  2755. case ( /grid_btn|text_editor|grid_btn|show_image_grid|show_font_grid/.test(id) ): break; // ignore these ids (i.e, don't get links)
  2756. case id === 'view_directory_source': break;
  2757. case ( /content_iframe_dir|open_sidebar_in_content_pane/.test(id) ): // prep for iframe dirs
  2758. $('body').addClass('focus_content');
  2759. link_info = getLinkInfo(args[0]);
  2760. link = link_info[0]; // set links for default (chrome) and gecko browsers
  2761. link += getLinkQueries('dir'), kind = link_info[3];
  2762. $content_pane.addClass('has_iframe_dir').removeClass('has_iframe_file');
  2763. $content_iframe.removeAttr('data-iframe_file_src'); // because it's not needed after opening a dir
  2764. break;
  2765. case id === 'content_iframe_file': // prep for iframe files
  2766. $('body').addClass('focus_content');
  2767. link_info = getLinkInfo(args[0]);
  2768. link = link_info[0]; // set links for default (chrome) and gecko browsers
  2769. kind = link_info[3];
  2770. switch(true) {
  2771. case kind === 'audio':
  2772. $content_pane.addClass('has_iframe_audio');
  2773. break;
  2774. default:
  2775. $content_pane.addClass('has_iframe_file');
  2776. $content_iframe.attr('data-iframe_file_src',$content_iframe.attr('src')); // remember iframe file parent directory
  2777. }
  2778. break;
  2779. case id !== '': // show content from sidebar items or iframe items (from above) and get link, kind
  2780. row = getElById(id); // link = getFormattedLink(row.find('a').attr('href'),row.attr('data-kind'));
  2781. link_info = getLinkInfo(row.find('a').attr('href')); kind = link_info[3];
  2782. link = link_info[0]; // set links for default (chrome) and gecko browsers
  2783. switch(true) {
  2784. case kind === 'dir': case kind === 'app': case ( /code|text|markdown/.test(kind) ): // add link queries for dirs and text
  2785. link = link + getLinkQueries(kind); break;
  2786. case ( /text|markdown|code/.test(kind) ):
  2787. // sendMessage('iframe','get_text_selection','',[row.attr('data-selection_start'),row.attr('data-selection_end')]);
  2788. break;
  2789. }
  2790. break;
  2791. }
  2792. let content_el = ( ['audio','font','image','pdf','video'].includes(kind) ? kind : 'iframe' );
  2793. switch(true) {
  2794. case window.top !== window.self:
  2795. break; // don't attempt to show content for iframe items when navigating
  2796. case row !== undefined && row.hasClass('ignored') && getSearchParam('ignore_ignored_items') === 'false': // try to open ignored files if they are not to be ignored
  2797. window.location = row.find('a').attr('href');
  2798. break;
  2799. case row !== undefined && row.hasClass('ignored') && getSearchParam('ignore_ignored_items') === 'true': // don't open ignored files if they are to be ignored
  2800. closeContent(); setContentTitle($('tr.selected').find('a').attr('href')); $content_pane.attr('data-content','has_ignored');
  2801. break;
  2802. case kind === 'link':
  2803. closeContent(); $content_pane.attr('data-loaded','loaded'); $content_iframe.addClass('has_content').attr('src',link).attr('data-kind',kind);
  2804. break;
  2805. case ( /grid_btn|show_image_grid|show_font_grid/.test(id) ): showGrid(id); break; // show grid
  2806. case id === 'text_editor' || id === 'text_editor_row': showTextEditor(); break; // show the text editor
  2807. case kind === 'audio': showAudio(id,link); break; // show audio
  2808. case kind === 'video': showVideo(id,link); // no break; close audio when showing video ???? test if needed?
  2809. case ['font','image','pdf','video'].includes(kind):
  2810. closeContent(); // Close all open content
  2811. setContentSources(id, kind, link, content_el); // Set the src attribute for the content element
  2812. setContentClasses(id, kind, content_el); // Add "data=has_<kind>" to content_pane; test id for content_iframe
  2813. setContentTitle($('#content_container .has_content:not(#content_iframe)').attr('src')); // no break; Set the title for non-iframe content
  2814. case kind === 'image': setImageDimensions(); break;
  2815. case id === 'view_directory_source': kind = 'view_directory_source'; link = current_location + '?&view_directory_source=true'; // no break;
  2816. case kind === 'dir' && id !== 'content_iframe_dir': // no break;
  2817. case kind === 'app' && id !== 'content_iframe_dir': // no break;
  2818. case content_el === 'iframe': // set iframe source and hide until iframe loaded
  2819. closeContent(); // Close all open content
  2820. $content_pane.attr('data-loaded','unloaded'); // hide iframe until loaded, show loading spinner
  2821. $content_iframe.addClass('has_content').attr('src',link).attr('data-kind',kind); // set iframe source, data-kind
  2822. if ( id !== 'view_directory_source' ) { $content_iframe.attr('data-content_name',link_info[0]); }
  2823. break;
  2824. }
  2825. if ( $('body').hasClass('focus_content') || id === 'content_iframe' ) { sendMessage('iframe','focus_iframe'); }
  2826. }
  2827. // Show source of current sidebar dir in content pane
  2828. function showDirectorySource() {
  2829. switch(true) {
  2830. case $body.hasClass('has_directory_source'): $('#close_btn').click(); break; // close if open
  2831. default: $body.addClass('has_directory_source'); showContent('view_directory_source',[current_location]);
  2832. }
  2833. }
  2834. $('#view_directory_source').on('click',function(e) { e.stopPropagation(); showWarning( 'showDirectorySource' ); }); // toggle show directory source
  2835. // Show current sidebar dir in content pane
  2836. function openSidebarInContentPane() { showContent('open_sidebar_in_content_pane',[current_location,'dir']); focusContent(); }
  2837. $('#open_in_content_pane').on('click',function() { showWarning( 'openSidebarInContentPane' ); });
  2838. function openInTextEditor() {
  2839. let scroll_script = '<script id="scroll_script">window.onscroll = function(){ window.parent.postMessage( { "messageContent":"scroll_iframe","functionName":"","arguments":window.scrollY },"*"); }</script>';
  2840. let html = $('#content_text').data('edit_html');
  2841. closeContent(); showTextEditor();
  2842. if ( html !== undefined ) { $('#text_source').val(html); $('#text_preview').attr( 'srcdoc',scroll_script + html ); $('#html_preview').val(html); } // set previewed text
  2843. $('#content_text').removeData('edit_html');
  2844. }
  2845. $('#open_in_text_editor').on('click',function() { sendMessage('iframe','get_html_content'); }); // send message to iframe, receive message from iframe, and then call openInTextEditor().
  2846. //***** CLOSE CONTENT (Close button or Cmd/Ctrl + W) *****//
  2847. // Close Audio/Video
  2848. function closeMedia(kind) { // type === audio || video
  2849. let $media_el = ( kind === 'audio' ? $audio_player : $content_video );
  2850. $media_el.trigger('pause').removeAttr('src');
  2851. switch(true) {
  2852. case kind === 'audio':
  2853. $('tr.audio_loaded').removeClass('audio_loaded');
  2854. $('#content_audio_title span').empty();
  2855. $('#content_audio_playlist').removeClass('has_content');
  2856. $('body').removeClass('is_playing is_paused');
  2857. $content_pane.removeClass('has_audio has_iframe_audio');
  2858. sendMessage('iframe','close_iframe_audio');
  2859. break;
  2860. case kind === 'video' && $('#content_pane').attr('data-content') === 'has_video':
  2861. $('tr.video.content_loaded').removeClass('content_loaded');
  2862. $('#content_pane').removeAttr('data-content').find('#content_video').removeClass('has_content');
  2863. break;
  2864. }
  2865. }
  2866. $('#close_audio').on('click',function(e) { e.stopPropagation(); closeMedia('audio'); }); // Close Audio button click
  2867. // Close Playlist
  2868. function closePlaylist(bool) { // bool === true: close all content
  2869. $('#tbody').empty().html($('#tbody').data('dir_list')); // restore original dir_list
  2870. $body.removeClass('has_playlist has_filelist has_media has_audio has_video has_fonts has_images has_menu')
  2871. .addClass( $('#tbody').data('data_classes').join(' ') ).find('#sort_by_date,li#date').removeClass('disabled'); // restore orignal body "has" classes
  2872. $('head').find('title').text('Index of: '+ current_location); // change window title back to default
  2873. $('#current_dir_path').find('span').empty().html(current_dir_path);
  2874. if ( $('#tbody').find('.media').length < 1 ) { $body.removeClass('has_media has_audio has_video'); }
  2875. if ( bool === true ) { closeContent(); }
  2876. $('tr.selected.playlist').find('a').click();
  2877. updateStats();
  2878. }
  2879. $('#close_playlist,#close_playlist_btn').on('click', function(e) { e.preventDefault(); e.stopPropagation(); showWarning('closePlaylist',true); }); // true === close all content
  2880. // Close index source preview
  2881. function closeIndexSource() {
  2882. $body.removeClass('has_directory_source');
  2883. $content_pane.removeAttr('data-content').removeAttr('data-loaded'); //
  2884. $content_iframe.removeAttr('src').removeClass('has_content'); // remove iframe src
  2885. $('#title span').empty(); // empty content title
  2886. if ( $('#tbody tr.content_loaded').length === 1 ) { $('#tbody tr.content_loaded').click(); }
  2887. focusSidebar();
  2888. }
  2889. // Close opened font file
  2890. function closeFontFile() {
  2891. $body.removeClass('faded focus_content');
  2892. $content_pane.removeAttr('data-content').find('#glyphs_container').empty();
  2893. $('#font_viewer').remove().end().append( content_font_viewer );
  2894. $('#title span').empty();
  2895. $('tr.content_loaded').click();
  2896. }
  2897. // Close glyph
  2898. function closeGlyph() {
  2899. $('#glyph_viewer').hide().attr('style','');
  2900. $content_pane.attr('data-content','has_font_file');
  2901. $content_font.addClass('has_content');
  2902. scrollThis('glyphs_container','selected');
  2903. }
  2904. // Close all .content elements before opening any new .content from sidebar; leave grid and text editor hidden.
  2905. function closeContent() {
  2906. switch(true) {
  2907. case window.top !== window.self:
  2908. break;
  2909. default: // remove sources, data-content, styles, and classes from #content_pane
  2910. $('#title span').removeAttr('data-after').empty(); // empty content title
  2911. hideGrid(); hideTextEditor(true); // hide grid and texteditor
  2912. $content_pane.removeClass('has_zoom_image has_scaled_image').removeAttr('data-content').removeAttr('data-loaded'); // remove content_pane classes and data
  2913. $content_pane.find('.has_content').removeClass('has_content').removeAttr('src').removeAttr('style'); // remove .content classes, data, and attributes
  2914. document.getElementById('content_iframe_utility').removeAttribute('src');
  2915. $content_font.find('#font_viewer').remove().end().append( content_font_viewer );
  2916. closeMedia('video');
  2917. $('#cue_sheet_track_list_container_video').removeClass('has_cue_sheet').empty(); // remove video cue sheet menu
  2918. setupContentPDF();
  2919. $('.playlist_entry_container').removeClass('has_content').find('textarea').val('');
  2920. }
  2921. }
  2922. //////// NEW CLOSE BUTTON FUNCTION: replaces OLDcloseContent()
  2923. function closeButton() {
  2924. switch(true) {
  2925. // CLOSE GRIDS & TEXT EDITOR
  2926. case $content_pane.attr('data-content') === 'has_grid': // close grid
  2927. closeGrid();
  2928. if ( $content_pane.hasClass('has_hidden_text_editor') ) { showHiddenTextEditor(); } else { focusSidebar(); }
  2929. break;
  2930. case $content_pane.attr('data-content') === 'has_text_editor': // hide text editor
  2931. hideTextEditor();
  2932. if ( $content_pane.hasClass('has_hidden_grid') ) { showHiddenGrid(); } else { focusSidebar(); }
  2933. break;
  2934. case $body.hasClass('iframe_edited'): // close edited iframe file
  2935. sendMessage('iframe','unloading');
  2936. break;
  2937. // CLOSE FILES AND DIRECTORIES
  2938. case $body.hasClass('has_directory_source'): // close directory source
  2939. closeIndexSource();
  2940. break;
  2941. case $content_pane.hasClass('has_iframe_file') && $content_pane.hasClass('has_iframe_dir'): { // close iframe file and reopen navigated container directory
  2942. showContent('content_iframe_dir',[$content_iframe.attr('data-iframe_file_src'),'source_dir']);
  2943. sendMessage('iframe','focus_iframe');
  2944. break;
  2945. }
  2946. case $content_pane.hasClass('has_iframe_file') && !$content_pane.hasClass('has_iframe_dir'):
  2947. // close iframe file opened from previewed sidebar directory (not navigated directory) and reopen selected sidebar directory
  2948. $content_pane.removeClass('has_iframe_file').removeAttr('data-content');
  2949. // no break;
  2950. case $content_pane.hasClass('has_iframe_dir'): // close navigated iframe directory and reopen selected sidebar directory
  2951. $content_pane.removeClass('has_iframe_dir');
  2952. clickRow($('#tbody tr.selected').attr('id')); // click selected sidebar item
  2953. sendMessage('iframe','focus_iframe');
  2954. break;
  2955. case $content_pane.attr('data-content') === 'has_dir': // close selected sidebar directory
  2956. $('tr.selected:not(.audio)').removeClass('selected content_loaded');
  2957. $content_pane.removeAttr('data-content').removeAttr('data-loaded'); //
  2958. $content_iframe.removeAttr('src').removeClass('has_content'); // remove iframe src
  2959. $('#title span').empty(); // empty content title
  2960. focusSidebar();
  2961. break;
  2962. // CLOSE FONTS & GLYPHS
  2963. case $content_pane.attr('data-content') === 'has_glyph': // close glyph
  2964. showWarning( 'closeGlyph' );
  2965. break;
  2966. case $content_pane.attr('data-content') === 'has_font_file': // close font file preview
  2967. showWarning( 'closeFontFile' );
  2968. break;
  2969. // CLOSE ALL OTHER CONTENT EXCEPT AUDIO
  2970. case $content_pane.attr('data-content') === 'has_ignored': // close ignored content
  2971. case $('#content_container .content').hasClass('has_content'): // close all other content
  2972. case $('#content_image').hasClass('has_content'): // close all other content
  2973. $('tr.selected').removeClass('content_loaded'); // don't remove selected in order to allow sidebar navigation to continue from closed file
  2974. $content_iframe.removeAttr('data-iframe_file_src').removeAttr('data-kind');
  2975. closeContent();
  2976. focusSidebar();
  2977. switch(true) {
  2978. case $content_pane.hasClass('has_hidden_grid'): showGrid(); break;
  2979. case $content_pane.hasClass('has_hidden_text_editor'): showTextEditor(); break;
  2980. }
  2981. break;
  2982. // CLOSE AUDIO & PLAYLISTS
  2983. case $content_pane.hasClass('has_audio'): // close audio
  2984. closeMedia('audio'); focusSidebar();
  2985. break;
  2986. case $body.hasClass('has_playlist'): // close playlist
  2987. case $body.hasClass('has_filelist'): // close filelist
  2988. showWarning( 'closePlaylist' );
  2989. break;
  2990. }
  2991. // show hidden texteditor or grid
  2992. }
  2993. // close button
  2994. $('#close_btn').on('click', function(e) { e.stopPropagation(); e.preventDefault(); showWarning('closeButton'); $(this).blur(); });
  2995. //***** RESET CONTENT (Reset button or Cmd/Ctrl + R) *****//
  2996. function reloadImage() {
  2997. $content_pane.removeClass('has_zoom_image has_scaled_image').find('#content_image_container,#content_image').removeAttr('style');
  2998. $('#content_image').removeAttr('src');
  2999. $('#tbody').find('.image.selected,.image.content_loaded').find('a').click(); // clicking allows changed content to be reloaded instead of just being reset
  3000. }
  3001. function resetContent() {
  3002. switch(true) {
  3003. case $content_pane.hasClass('has_audio'): // pause and reset media
  3004. case $content_pane.attr('data-content') === 'has_video':
  3005. $audio_player.prop('currentTime', 0).trigger('pause');
  3006. $content_video.prop('currentTime',0).trigger('pause');
  3007. break;
  3008. }
  3009. switch(true) {
  3010. case $content_pane.attr('data-content') === 'has_text_editor':
  3011. case $body.hasClass('has_playlist'):
  3012. break; // don't do anything else for audio, video, text editor, playlist content.
  3013. case $content_pane.attr('data-content') === 'has_grid':
  3014. $('#grid_btn').click();
  3015. break;
  3016. case $content_pane.attr('data-content') === 'has_glyph':
  3017. $('#glyph_viewer').css({'width':'100%','height':'100%','background-size':'100%'}).attr('data-scale',1);
  3018. break;
  3019. case $content_pane.attr('data-content') === 'has_font':
  3020. $content_font.css({'font-size':'1em'});
  3021. break;
  3022. case $content_pane.attr('data-content') === 'has_image':
  3023. reloadImage();
  3024. break;
  3025. case ( /has_markdown|has_htm|has_iframe|has_dir/.test( $content_pane.attr('data-content') ) ):
  3026. $('#tbody').find('.selected').find('a').click(); // clicking allows changed content to be reloaded instead of just being reset
  3027. break;
  3028. case $content_pane.hasClass('has_audio'):
  3029. case $content_pane.hasClass('has_video'):
  3030. break; // don't do anything else for audio, video, text editor, playlist content.
  3031. case ( /has_ignored|undefined/.test( $content_pane.attr('data-content') ) ):
  3032. window.location = window.location.href;
  3033. break;
  3034. }
  3035. }
  3036. $('#reload_btn').on('click', function(e) { e.stopPropagation(); e.preventDefault(); showWarning('resetContent'); $(this).blur().removeClass('reset'); });
  3037. //**********************//
  3038. //***** NAVIGATION *****//
  3039. function firstRowID(class_name) { return ( $('#tbody').find('> tr:visible').filter(class_name).length ? $('#tbody').find('> tr:visible').filter(class_name).first().attr('id') : null ); }
  3040. function lastRowID(class_name) { return ( $('#tbody').find('> tr:visible').filter(class_name).length ? $('#tbody').find('> tr:visible').filter(class_name).last().attr('id') : null ); }
  3041. function prevRowID(class_name) {
  3042. let $selected, row_ID;
  3043. if ( /audio/.test(class_name) ) { $selected = $('tr.audio.selected,tr.audio_loaded'); } else { $selected = $('#tbody').find('.selected'); }
  3044. switch(true) {
  3045. case !$selected.length:
  3046. case !$selected.prevAll('tr:visible:not(.disabled):not(.unchecked)').filter(class_name).length:
  3047. row_ID = $('#tbody').find('> tr:visible:not(.disabled):not(.unchecked)').filter(class_name).last().attr('id'); // select last item if nothing selected or selected is first item
  3048. break;
  3049. default:
  3050. row_ID = $selected.prevAll('tr:visible:not(.disabled):not(.unchecked)').filter(class_name).first().attr('id');
  3051. }
  3052. return row_ID;
  3053. }
  3054. function nextRowID(class_name,bool) { // if nothing selected, or if no next row with class_name, return first row with class_name, else return next row with class_name; bool --> autoplay audio
  3055. let $selected, row_ID;
  3056. if ( bool === 'true' ) { $selected = $('tr.audio.selected,tr.audio_loaded'); class_name === 'audio'; } else { $selected = $('#tbody').find('tr.selected'); }
  3057. switch(true) {
  3058. case !$selected.length:
  3059. case !$selected.nextAll('tr:visible:not(.disabled):not(.unchecked)').filter(class_name).length: //
  3060. row_ID = $('#tbody').find('> tr:visible:not(.disabled):not(.unchecked)').filter(class_name).first().attr('id'); // select last item if nothing selected or selected is first item
  3061. break;
  3062. default:
  3063. row_ID = $selected.nextAll('tr:visible:not(.disabled):not(.unchecked)').filter(class_name).first().attr('id');
  3064. }
  3065. return row_ID;
  3066. }
  3067. // get selected row id
  3068. function selectRowID(class_name,key,bool) { // bool === true --> autoplay media
  3069. let id = '';
  3070. switch(key) {
  3071. case 'ArrowUp': id = prevRowID('.dir,.file'); break;
  3072. case 'ArrowLeft': id = prevRowID(class_name); break;
  3073. case 'ArrowRight': id = nextRowID(class_name,bool); break;
  3074. case 'ArrowDown': id = nextRowID('.dir,.file'); break;
  3075. }
  3076. return id;
  3077. }
  3078. // NAVIGATION: Menu Arrow Navigation
  3079. function menuNavigation(key) {
  3080. let $selected_menu_item = $('#menu').find('ul li.selected');
  3081. switch(key) {
  3082. case 'ArrowUp':
  3083. switch(true) {
  3084. case $('#menu').find('.hovered').length > 0: // if submenu visible
  3085. switch(true) { // if no menu item selected, select last submenu item, else select previous submenu item
  3086. case !$selected_menu_item.length: $('#menu').find('ul li:last-of-type').addClass('selected'); break;
  3087. default: $selected_menu_item.removeClass('selected').prevAll('li:visible').first().addClass('selected');
  3088. }
  3089. break;
  3090. case !$('#menu').find('.selected').length: $('#menu').find('> li:last-of-type').addClass('selected'); break; // select last item
  3091. default: $('#menu').find('.selected:not(.hovered)').removeClass('selected').prevAll('li:visible').first().addClass('selected');
  3092. }
  3093. break;
  3094. case 'ArrowDown':
  3095. switch(true) {
  3096. case $('#menu').find('.hovered').length > 0: // if submenu visible
  3097. switch(true) { // if no menu item selected, select first submenu item, else select next submenu item
  3098. case !$selected_menu_item.length: $('#menu').find('ul li:first-of-type').addClass('selected'); break;
  3099. default: $selected_menu_item.removeClass('selected').nextAll('li:visible').first().addClass('selected');
  3100. }
  3101. break;
  3102. case !$('#menu').find('.selected').length: $('#menu').find('> li:first-of-type').addClass('selected'); break;
  3103. default: $('#menu').find('.selected:not(.hovered)').removeClass('selected').nextAll('li:visible').first().addClass('selected');
  3104. }
  3105. break;
  3106. case 'ArrowLeft':
  3107. switch(true) {
  3108. case !$('#menu').find('.selected').length: $('#menu').find('> li:last-of-type').addClass('selected'); break;
  3109. case $('#menu').find('li.selected').hasClass('hovered'): $('#menu').find('.hovered').removeClass('hovered'); $('#menu').find('ul li').removeClass('selected'); break;
  3110. }
  3111. break;
  3112. case 'ArrowRight':
  3113. switch(true) {
  3114. case !$('#menu').find('.selected').length: $('#menu').find('> li:first-of-type').addClass('selected'); break;
  3115. case $('#menu').find('li.selected').hasClass('has_submenu') && !$('.submenu').find('.selected').length:
  3116. $('#menu').find('.selected').addClass('hovered').find('ul li:first-of-type').addClass('selected'); break;
  3117. }
  3118. break;
  3119. }
  3120. }
  3121. // NAVIGATION: select GRID items by left/right arrow keys @ arrowNavigation();
  3122. function gridNavigation(elId,key) {
  3123. const $container_el = $(elId);
  3124. let $selected = $container_el.find('> .selected');
  3125. let grid_item_id; // = grid item data-id = corresponding dir_list item id.
  3126. let row_length = 0; // $content_grid.hasClass('has_grid') || $('#content_font').hasClass('has_font_grid')
  3127. row_length = ( Math.round( $container_el.innerWidth() / $container_el.find('> div').outerWidth()) - 1 );
  3128. switch(true) {
  3129. case $selected.length === 0: // nothing selected
  3130. switch(true) {
  3131. case key === 'ArrowRight': case key === 'ArrowDown': $container_el.find('> div').first().addClass('selected'); break;
  3132. case key === 'ArrowLeft': case key === 'ArrowUp': $container_el.find('> div').last().addClass('selected'); break;
  3133. }
  3134. grid_item_id = $container_el.find('.selected').attr('data-id');
  3135. break;
  3136. case key === 'ArrowUp':
  3137. switch(true) {
  3138. case $selected.prevAll().eq(row_length).length === 1: grid_item_id = $selected.prevAll().eq(row_length).attr('data-id'); break;
  3139. case $container_el.find('> div').length - 1 === row_length: gridNavigation(elId,'ArrowLeft'); break; // for single grid row, select prev item
  3140. default: grid_item_id = $container_el.find('> div').last().attr('data-id'); break;
  3141. }
  3142. break;
  3143. case key === 'ArrowDown':
  3144. switch(true) {
  3145. case $selected.nextAll().eq(row_length).length === 1: grid_item_id = $selected.nextAll().eq(row_length).attr('data-id'); break;
  3146. case $container_el.find('> div').length - 1 === row_length: gridNavigation(elId,'ArrowRight'); break; // for single grid row, select next item
  3147. default: grid_item_id = $container_el.find('> div').first().attr('data-id'); break;
  3148. }
  3149. break;
  3150. case key === 'ArrowLeft':
  3151. switch(true) {
  3152. case $selected.prev().length === 1: grid_item_id = $selected.prev().attr('data-id'); break;
  3153. default: grid_item_id = $container_el.find('> div').last().attr('data-id'); break;
  3154. }
  3155. break;
  3156. case key === 'ArrowRight':
  3157. switch(true) {
  3158. case $selected.next().length === 1: grid_item_id = $selected.next().attr('data-id'); break;
  3159. default: grid_item_id = $container_el.find('> div').first().attr('data-id'); break;
  3160. }
  3161. break;
  3162. }
  3163. selectThis(grid_item_id);
  3164. switch(true) {
  3165. case $content_pane.attr('data-content') === 'has_glyph': $('.glyph_container.selected .glyph').click(); break; // show the selected glyph
  3166. case $content_pane.hasClass('has_hidden_grid'): $('#content_grid > div[data-id="'+ grid_item_id +'"]').click(); break;
  3167. }
  3168. }
  3169. // NAVIGATION: FONTS and IMAGES by prev/next buttons
  3170. $content_pane.on( 'click','#prev_btn, #next_btn', function(e) {
  3171. e.stopPropagation(); e.preventDefault();
  3172. let key = $(this).attr('id') === 'prev_btn' ? 'ArrowLeft' : 'ArrowRight';
  3173. switch(true) {
  3174. case $content_pane.attr('data-content') === 'has_font_viewer': gridNavigation('#glyphs_container',key); break;
  3175. default: clickRow( selectRowID('.font,.image',key));
  3176. }
  3177. document.getElementById('prev_next_btns').blur();
  3178. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  3179. });
  3180. // NAVIGATION: Audio by prev/next audio buttons
  3181. $('.prev_next_track_btn').on('click',function() { let key = ( $(this).attr('id') === 'prev_track' ? 'ArrowLeft' : 'ArrowRight' ); playPrevNextMediaTrack(key); });
  3182. function upDownArrowNavigation(e) {
  3183. switch(true) {
  3184. case ( /a|button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ) && window.top !== window.self ):
  3185. case ( /text_preview|html_preview/.test(document.activeElement.id) ):
  3186. return; // don't navigate if text editor previews are focussed
  3187. default:
  3188. document.activeElement.blur();
  3189. }
  3190. e.preventDefault();
  3191. document.activeElement.blur(); $('body').focus(); // Need this to able to select grid items after clicking close button (or other buttons).
  3192. let row_ID = selectRowID('.dir:visible,.file:visible',e.key);
  3193. let row = $(document.getElementById(row_ID));
  3194. let $selected = $('#tbody').find('.selected');
  3195. switch(true) {
  3196. case cmdKey(e) && e.key === 'ArrowUp': // Cmd/Ctrl + up arrow
  3197. switch(true) {
  3198. case window.self !== window.top: $('#iframe_body #parent').find('a').click(); break; // go to iframe parent
  3199. case window.self === window.top && $('body').hasClass('focus_content'): // fallback for go to iframe parent in case top is incorrectly focused
  3200. sendMessage('iframe','open_iframe_parent_dir'); break;
  3201. default: showWarning( 'clickThis', $('#parent_dir_nav').attr('id') ); break; // go to parent (with warning)
  3202. }
  3203. break;
  3204. case cmdKey(e) && e.key === 'ArrowDown': // Cmd/Ctrl + down arrow
  3205. switch(true) {
  3206. case window.self === window.top && $('body').hasClass('focus_content'): focusContent('content_iframe',e); break; // select first item if nothing selected in iframe
  3207. case window.self === window.top && $selected.hasClass('file') && !$selected.hasClass('link'): break; // do nothing for #top files
  3208. case window.self === window.top && $selected.hasClass('dir') && $selected.hasClass('app') && $settings.apps_as_dirs === false: break; // do nothing for apps if not viewing as dirs
  3209. default: $selected.find('a').trigger('dblclick'); // else double-click directories and all iframe items to open them
  3210. }
  3211. break;
  3212. case $body.hasClass('has_directory_source'): // if viewing directory source, arrows will reopen selected sidebar item
  3213. if ( $('#tbody .content_loaded').length > 0 ) { clickRow( $('#tbody .content_loaded').attr('id') ); } else { clickRow( row_ID ); }
  3214. scrollThis('tbody','selected');
  3215. break;
  3216. case cmdKey(e) && window.self !== window.top: // iframe && cmdKey
  3217. switch(true) {
  3218. case $('#iframe_body #dir_list').length === 0:
  3219. break;
  3220. }
  3221. break;
  3222. case window.top === window.self && $('body').hasClass('focus_content'):
  3223. $content_iframe.focus();
  3224. focusContent('iframe',e);
  3225. sendMessage('iframe','iframe_arrow_navigation','',e.key);
  3226. $('#iframe_body a').click();
  3227. break;
  3228. default: // click prev/next item
  3229. selectThis(row_ID); // don't use $selected after this
  3230. switch(true) {
  3231. case row.hasClass('audio'): break; // only select row
  3232. default: showContent(row_ID); // otherwise show content
  3233. }
  3234. if ( $('.selected').length ) { scrollThis('tbody','selected'); }
  3235. }
  3236. }
  3237. // NAVIGATION: Prev/Next Audio or Prev/Next File of same data-kind by arrow left/right
  3238. function leftRightArrowNavigation(e) {
  3239. let args = [e.key]; if ( e.shiftKey ) { args.push(30); } else { args.push(10); } // for mediaSkip
  3240. switch(true) { // various fixes for various situations
  3241. case ( /content_iframe/.test(document.activeElement.id) && ( e.altKey || e.shiftKey ) ): sendMessage('top','mediaSkip','',args); return;
  3242. case ( /content_iframe/.test(document.activeElement.id) ) && !/shiftkey|metakey|altkey/.test(e.key.toLowerCase()): sendMessage('iframe','play_prev_next_iframe_audio','',e.key); return;
  3243. case ( /a|button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ) && window.top !== window.self ): // no break
  3244. case ( /text_preview|html_preview|content_iframe/.test(document.activeElement.id) ):
  3245. return; // don't navigate if iframe content or text editor previews are focussed
  3246. default:
  3247. // document.activeElement.blur(); // $('tr.selected').find('a').focus();
  3248. }
  3249. switch(true) {
  3250. case cmdKey(e): // Cmd + arrowkey:
  3251. switch(true) {
  3252. case e.key === 'ArrowRight' && $('tr.selected').hasClass('dir'): $('tr.dir.selected').find('.has_icon_before_before').click(); break; // open subdirectory
  3253. case e.key === 'ArrowLeft' && $('tr.selected').hasClass('has_subdirectory'): closeSubdirectory($('tr.dir.selected').attr('id') ); break; // close subdir
  3254. case e.key === 'ArrowLeft' && !$('tr.selected').hasClass('has_subdirectory') && $('tr.has_subdirectory').length > 0:
  3255. clickRow($('tr.selected').prevAll('.has_subdirectory').first().attr('id')); break; // jump to parent directory
  3256. default: clickRow($('tr:visible').first().attr('id')); // jump to first visible item
  3257. }
  3258. scrollThis('tbody','selected');
  3259. break;
  3260. case cmdAltKey(e) && e.key === 'ArrowLeft': case cmdAltKey(e) && e.key === 'ArrowRight': // don't start audio tracks when changing tabs
  3261. break;
  3262. default:
  3263. e.preventDefault();
  3264. let $selected = $('#tbody').find('.selected');
  3265. switch(true) {
  3266. case e.altKey && e.shiftKey: // L/R Arrow + Alt + Shift
  3267. case e.altKey && !e.metaKey && !e.ctrlKey: { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  3268. switch(true) {
  3269. case window.top !== window.self: sendMessage('top','mediaSkip','mediaSkip',args); break;
  3270. default: mediaSkip(e);
  3271. }
  3272. break;
  3273. }
  3274. case $body.hasClass('has_directory_source'): // if viewing directory source
  3275. switch(true) {
  3276. case $('#tbody .content_loaded').length > 0: clickRow( $('#tbody .content_loaded').attr('id') ); break;
  3277. default: clickRow( selectRowID('.dir,.file',e.key) );
  3278. }
  3279. scrollThis('tbody','selected');
  3280. break;
  3281. default:
  3282. switch(true) {
  3283. case $selected.hasClass('media') && $body.hasClass('play_all_media'): // no break; play all media
  3284. case $selected.hasClass('audio'): playPrevNextMediaTrack(e.key); break; // play prev/next media track
  3285. case $selected.length === 0: clickRow( selectRowID('.dir,.file',e.key) ); break; // if nothing selected, click first/last row
  3286. default: clickRow( selectRowID('.'+ $selected.attr('data-kind'),e.key) ); // click prev/next item with same data-kind
  3287. if ( $selected.hasClass('video') ) { playMedia('play'); }
  3288. }
  3289. scrollThis('tbody','selected');
  3290. }
  3291. }
  3292. }
  3293. // NAVIGATION: AUDIO tracks by arrow left/right @ leftRightArrowNavigation() & button click; // handle shuffle or loop play, as well as normal continuous playback after end of track with ArrowRight
  3294. function playPrevNextMediaTrack(key,bool) { // bool === 'true' if from initMedia clickRow
  3295. let track_row_id, $selected_media;
  3296. switch(true) { // define $selected_media
  3297. case bool === 'true': // autoplay media (if selected audio !== playing audio, select selected audio, else select loaded media)
  3298. $selected_media = ( $('#tbody tr.audio.selected').length === 1 && $('#tbody tr.audio_loaded') !== $('#tbody tr.audio.selected') ? $('#tbody tr.audio.selected') : $('#tbody tr.audio_loaded,#tbody tr.video.content_loaded') );
  3299. break;
  3300. default: $selected_media = $('#tbody tr.media.selected'); // normal l/r arrow play media
  3301. }
  3302. // if play all media, use .media, else use .selected:
  3303. let media_class = ( $('body').hasClass('play_all_media') ? '.media:not(.ignored):not(.unchecked)' : '.'+ $selected_media.attr('data-kind') +':not(.ignored):not(.unchecked)' );
  3304. let selected_row_id = $selected_media.attr('id'), last_row_id = lastRowID( media_class );
  3305. switch(true) {
  3306. case $selected_media.length === 1 && !/loaded/.test($selected_media[0].classList.value): // if selected media track is not playing, click and play
  3307. switch(true) {
  3308. case window.self !== window.top: $selected_media.find('a').trigger('dblclick'); sendMessage('top','iframe_play_pause_media'); break;
  3309. default:
  3310. if ( media_class.indexOf('audio') > -1 ) { showAudio( selected_row_id ); } else { clickRow( selected_row_id ); }
  3311. playMedia('play');
  3312. }
  3313. break;
  3314. case $('.audio_loaded').length === 0 && $('.video.content_loaded').length === 0: // Arrow L/R selects first/last media file if nothing selected
  3315. clickRow( selectRowID('.audio_loaded',key) ); playMedia('play');
  3316. break;
  3317. case $selected_media.length === 1: // if an media file is loaded...
  3318. switch(true) {
  3319. case $body.hasClass('shuffle_media'): // and if shuffle play is checked...
  3320. track_row_id = $audio_player.data('shufflelist').pop(); // remove track from shuffleTrackList
  3321. switch(true) {
  3322. case track_row_id !== undefined: // if shuffle list is not empty...
  3323. if ( document.getElementById(track_row_id).classList.contains('audio') ) { showAudio( track_row_id ); } else { clickRow( track_row_id ); } playMedia('play'); break; // load media and play
  3324. case track_row_id === undefined: // else if end of shufflelist...
  3325. updateShuffleList();
  3326. switch(true) {
  3327. case $body.hasClass('loop_media'): playMedia('play'); break; // and if loop audio, update the shufflelist and play
  3328. default: clickRow( $('tr.media:not(.ignored)').first().attr('id') ); // else just load the first track
  3329. }
  3330. }
  3331. break;
  3332. default: // ordinary playback: play prev/next track
  3333. clickRow( selectRowID( media_class,key ) );
  3334. switch(true) {
  3335. case window.self !== window.top: // iframe audio files
  3336. $('.selected.media').find('a').trigger('dblclick');
  3337. sendMessage('top','iframe_play_pause_media');
  3338. break; // show iframe audio
  3339. case key === 'ArrowRight' && selected_row_id === last_row_id && bool === 'true': // loop audio: autoplay first track at end of last track
  3340. if ( $body.hasClass('loop_media') ) { playMedia('play'); }
  3341. break;
  3342. default:
  3343. playMedia('play'); break; // play next track, don't play first track after last
  3344. }
  3345. break;
  3346. }
  3347. break;
  3348. }
  3349. scrollThis('tbody','audio_loaded');
  3350. scrollThis('tbody','.video.content_loaded');
  3351. }
  3352. // NAVIGATE directory index items by arrow up/down or left/right keys, with/without grid @ indexNavigation()
  3353. function arrowNavigation(e) {
  3354. if ( $('body').hasClass('focus_content') && !$('body').hasClass('has_menu') ) { focusContent(); }
  3355. switch(true) {
  3356. case window.top !== window.self && document.activeElement.id === 'iframe_body' && $('#iframe_body').find('#dir_list').length === 0: // allow arrows to work normally in iframes (but not iframe dirs)
  3357. case ( /img|textarea|embed/.test( document.activeElement.tagName.toLowerCase() ) ): // allow arrows to work normally in images and textareas
  3358. case document.activeElement.hasAttribute('contentEditable'): // allow arrows to work normally in contentEditable elements
  3359. break;
  3360. default:
  3361. e.preventDefault(); // otherwise prevent default arrow behavior
  3362. document.activeElement.blur(); // Need this to able to select grid items after clicking close button (or other buttons).
  3363. case $('body').hasClass('has_menu'): // menu navigation
  3364. if ( window.top !== window.self ) { sendMessage('top','menu_navigation','menuNavigation',e.key); } else { menuNavigation(e.key); }
  3365. break;
  3366. case $content_pane.attr('data-content') === 'has_grid' && $('body').hasClass('focus_content') && !cmdKey(e): // navigate grid
  3367. case $content_pane.attr('data-content') === 'has_image' && $content_pane.hasClass('has_hidden_grid') && !cmdKey(e): // navigate hidden grid
  3368. case $content_pane.attr('data-content') === 'has_font' && $content_pane.hasClass('has_hidden_grid') && !cmdKey(e): // navigate hidden grid
  3369. gridNavigation('#content_grid',e.key);
  3370. if ( !$content_pane.hasClass('has_hidden_grid') ) { scrollThis('content_grid','selected'); }
  3371. scrollThis('tbody','selected');
  3372. break;
  3373. case $content_pane.attr('data-content') === 'has_font_file' && !cmdKey(e): // navigate font file glyphs
  3374. case $content_pane.attr('data-content') === 'has_glyph' && !cmdKey(e): // navigate font file glyphs
  3375. if ( $('body').hasClass('focus_content') ) {
  3376. gridNavigation('#glyphs_container',e.key);
  3377. scrollThis('content_container','selected');
  3378. } else {
  3379. showWarning('warning_close_font','warning_btn_cancel'); // show close font warning if sidebar focused
  3380. }
  3381. break;
  3382. case e.key === 'ArrowUp' || e.key === 'ArrowDown': // up/down arrow navigation
  3383. upDownArrowNavigation(e);
  3384. break;
  3385. case e.key === 'ArrowLeft' || e.key === 'ArrowRight': { // left/right arrow navigation
  3386. leftRightArrowNavigation(e);
  3387. break;
  3388. }
  3389. }
  3390. }
  3391. // NAVIGATION: by typed string
  3392. var str = '';
  3393. function timeoutID() { return window.setTimeout( function() { str = ''; }, 1500 ); }
  3394. function alphaNav(e) { // Select Dir_List row by typed string; Todo: select next row by nearest letter
  3395. switch(true) {
  3396. case document.activeElement.tagName.toLowerCase() === 'textarea' || document.activeElement.getAttribute('contentEditable') === true: break;
  3397. default:
  3398. let timer = timeoutID();
  3399. if ( typeof timer === 'number' ) { window.clearTimeout( timer ); timer = 0; } // id
  3400. timeoutID();
  3401. str += e.key;
  3402. str = str.toLowerCase();
  3403. if ( $('#dir_list').find('td.name[data-name^="'+ str +'"]').length ) {
  3404. $('#dir_list').find('td.name[data-name^="'+ str +'"]').first().find('a').click();
  3405. scrollThis('tbody','selected');
  3406. // } else {
  3407. // null; // replace this with some sort of fuzzy match function? TBD
  3408. }
  3409. }
  3410. }
  3411. //***** END NAVIGATION *****//
  3412.  
  3413. // SELECT ROW on click and set classes for $content_pane
  3414. function selectThis(row_ID) {
  3415. let row = $(getElById(row_ID));
  3416. switch(true) {
  3417. case row.hasClass('disabled'):
  3418. break;
  3419. case $content_pane.attr('data-content') === 'has_grid':
  3420. case $content_pane.hasClass('has_hidden_grid'): {// Select corresponding grid item
  3421. const $grid_selected = $content_grid.find('> div[data-id="'+ row_ID +'"]');
  3422. row.addClass('selected').siblings().removeClass('selected hovered');
  3423. $grid_selected.addClass('selected').siblings().removeClass('selected');
  3424. break;
  3425. }
  3426. case row.hasClass('audio'):
  3427. row.addClass('selected').siblings().removeClass('selected');
  3428. break;
  3429. default:
  3430. $body.removeClass('has_directory_source');
  3431. row.addClass('content_loaded').siblings().removeClass('content_loaded');
  3432. row.addClass('selected').siblings().removeClass('selected hovered'); // remove classes from sibling, but leave .audio with playing
  3433. }
  3434. }
  3435. // CLICK element by id
  3436. function clickThis(id) { let el = $(document.getElementById(id)); if ( el.find('a').length > 0 ) { el.find('a').click(); } else { el.click(); } }
  3437. // CLICK Row to show content
  3438. function clickRow(id) { selectThis(id); showContent(id); }
  3439. // On click row, play/pause media or clickRow
  3440. $('#top #tbody').on('click','tr', function(e) {
  3441. e.preventDefault(); e.stopPropagation();
  3442. focusSidebar();
  3443. switch(true) {
  3444. case $(this).hasClass('audio'):
  3445. if ( !$(this).hasClass('selected') ) { showContent($(this).attr('id')); } else { playPauseMedia(); }
  3446. break;
  3447. case $(this).hasClass('video'):
  3448. if ( !$(this).hasClass('selected') ) { showWarning( 'clickRow', $(this).attr('id') ); } else { playPauseMedia(); }
  3449. break;
  3450. default: showWarning( 'clickRow', $(this).attr('id') );
  3451. }
  3452. });
  3453.  
  3454. // SUBDIRECTORIES
  3455. function openSubdirectory(parent) { // use the clicked subdirectory href as src for utility iframe, which loads that directory, processes the index and sends it back to the top iframe
  3456. let this_id = parent.attr('id'), parent_link = parent.find('a').attr('href'), level = Number(parent.attr('data-level')) + 1;
  3457. let content_iframe_utility_src = parent_link + getLinkQueries('dir') + '&parent_id='+ this_id +'&subdirectory=true&level='+ level;
  3458. $('#content_iframe_utility').attr('src',content_iframe_utility_src );
  3459. parent.addClass('dir_list_subdir_loading'); // removed when content_iframe_utility sends loaded message with subdirectory data
  3460. }
  3461. function closeSubdirectory(id) {
  3462. if ( id === undefined ) { // close all subdirectories
  3463. if ( $('#tbody tr.has_subdirectory').length > 0 ) { closeContent(); }
  3464. $('#tbody tr.dir').removeClass('has_subdirectory dir_list_subdir_loading');
  3465. $('#tbody').find('tr[id*="_"]').remove(); // remove any row whose id contains an underscore (= subdirectory row)
  3466. } else {
  3467. document.getElementById(id).classList.remove('has_subdirectory');
  3468. $('#tbody').find('tr').each(function() { if ( $(this).attr('id').startsWith(id + '_') ) { $(this).remove(); } }); // remove all rows whose id begins with the subdirectory parent id
  3469. }
  3470. updateStats();
  3471. }
  3472. $('#tbody').on('click','tr.dir span.has_icon_before_before',function(e) { // open/close subdirectories
  3473. e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
  3474. let $parent = $(this).closest('tr');
  3475. $('body').removeClass('has_stats');
  3476. switch(true) {
  3477. case ( /\.trashes|\.temporaryitems|\.spotlight-v\d+/.test($parent.find('.name').attr('data-name') ) ): $parent.removeClass('dir_list_subdir_loading'); break;
  3478. default:
  3479. selectThis($parent.attr('id'));
  3480. if ( $parent.hasClass('has_subdirectory') ) { closeSubdirectory( $parent.attr('id') ); } else { openSubdirectory($parent); }
  3481. if ( window.top === window.self ) { focusSidebar(); } else { document.activeElement.blur(); $('body').focus(); }// else { focusSidebar(); }
  3482. }
  3483. });
  3484. // DOUBLE-CLICK Row to open directory
  3485. function doubleClickRow(args) { // row.dir only
  3486. let row = document.getElementById(args[0]);
  3487. 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)
  3488. let id = args[0].slice(args[0].indexOf('-') + 1), href = args[1];
  3489. let query_str = decodeURIComponentSafe(window.location.search);
  3490. if ( query_str === '' ) { query_str = '?'; }
  3491. if ( query_str.indexOf('history') !== -1 ) {
  3492. query_str = query_str.replace(/&selected=\d+/,'').replace(/&history=/,'&history='+ id +'+');
  3493. } else {
  3494. query_str = query_str.replace(/&selected=\d+/,'') + '&history='+ id;
  3495. }
  3496. window.location = href + query_str;
  3497. }
  3498. $('#top').on('dblclick', '#tbody tr.dir,#tbody tr.link,#tbody tr.playlist', function(e) {
  3499. e.preventDefault(); e.stopPropagation();
  3500. switch(true) {
  3501. case $(this).hasClass('link'):
  3502. if ( $protocol === 'file:' ) { sendMessage('iframe','open_link_file','',$(this).attr('data-ext')); } else { openLink($(this).attr('data-ext')); }
  3503. break;
  3504. case $(this).hasClass('playlist'): {
  3505. let playlist_data = $(this).data('playlist');
  3506. openPlaylist('','',playlist_data);
  3507. }
  3508. break;
  3509. default:
  3510. showWarning('doubleClickRow', [$(this).attr('id'), $(this).find('a').attr('href')] );
  3511. }
  3512. });
  3513. // click content header or audio to remove faded body class
  3514. $('#sidebar_title,#sidebar_buttons,#sidebar,#sorting .sorting').on('click',function() { focusSidebar(); });
  3515. // CLICK grid item
  3516. $content_grid.on('click','div', function(e) {
  3517. e.preventDefault();
  3518. let id = $(this).attr('data-id');
  3519. $(this).addClass('selected').siblings().removeClass('selected');
  3520. hideGrid();
  3521. getElById(id).click();
  3522. });
  3523. // HOVER Grid Item and highlight dir_list row
  3524. $content_grid.on('mouseenter','> div:not(.selected)',function() {
  3525. document.getElementById(this.getAttribute('data-id')).classList.add('hovered');
  3526. }).on('mouseleave','> div:not(.selected)',function() {
  3527. document.getElementById(this.getAttribute('data-id')).classList.remove('hovered');
  3528. });
  3529. // HOVER Dir_list_row: highlight corresponding grid item
  3530. $dir_list_body.on('mouseenter','> tr', function() {
  3531. if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_grid.find('> div[data-id="'+ $(this).attr('id') +'"]').addClass('hovered'); }
  3532. }).on('mouseleave','> tr', function() {
  3533. if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_grid.find('> div[data-id="'+ $(this).attr('id') +'"]').removeClass('hovered'); }
  3534. });
  3535. // HOVER Footer Links: Fade dir_list
  3536. $('#footer_links').on('mouseenter',function() {
  3537. $('body:not(.has_menu), body:not(.has_menu_parents)').addClass('faded');
  3538. }).on('mouseleave',function() {
  3539. $('body:not(.has_menu), body:not(.has_menu_parents)').removeClass('faded');
  3540. });
  3541. // CLICK/HOVER Stats Items: get the hovered stats item class
  3542. function getHoveredStatsListClass(stats_item) {
  3543. stats_item.classList.remove('file','media');
  3544. let this_class = '.'+ Array.from(stats_item.classList).join('.');
  3545. switch(true) {
  3546. case $(stats_item).attr('id') === 'stats_summary_detailed_dirs': this_class = '.dir'; break;
  3547. case $(stats_item).attr('id') === 'stats_summary_detailed_files': this_class = '.file'; break;
  3548. // case /_/.test(this_class): this_class = this_class.replace('_','.'); break;
  3549. case this_class === '.dir': this_class = '.dir:not(.ignored):not(.invisible):not(.app)'; break;
  3550. case this_class === '.dir.app': this_class = '.dir.app:not(.ignored):not(.invisible)'; break;
  3551. }
  3552. return this_class;
  3553. }
  3554. // HOVER Stats Items: highlight dir_list items
  3555. $('#tfoot').on('mouseenter','#stats_details tr, #stats_summary_detailed_dirs, #stats_summary_detailed_files', function() {
  3556. $('#tbody').find(getHoveredStatsListClass(this)).addClass('hovered'); // add the hovered class
  3557. if ( $('#tbody .hovered').length > 0 ) { $('#tbody .hovered')[0].scrollIntoView({ behavior:'smooth', block:'start', inline:'nearest' }); } // scroll first matching element into view
  3558. }).on('mouseleave','tr',function() {
  3559. $('#tbody tr').removeClass('hovered');
  3560. });
  3561. $('#tfoot').on('click',function() { $('#tbody tr').removeClass('hovered'); });
  3562. // CLICK Stats footer detail items: Select the first matched kind and sort by kind
  3563. $('#tfoot').on('click','#stats_details tr, #stats_summary_detailed_dirs, #stats_summary_detailed_files', function() {
  3564. if ( !$('body').hasClass('sort_by_kind') ) { document.getElementById('sort_by_kind').click(); } // sort by kind
  3565. $('#tbody').find(getHoveredStatsListClass(this)).first().find('a').click(); // click the first item
  3566. scrollThis('tbody','selected',false); // scroll first item into view
  3567. });
  3568. // END CLICK & HOVER EVENTS
  3569.  
  3570. //***** AUTOLOAD CONTENT: index files or files from the file shortcut list *****//
  3571. function autoLoadFile() {
  3572. if ( window.self === window.top && getSearchParams().has('selected') ) { // add selected class
  3573. $('#tbody tr[id="rowid-'+ Number(getSearchParam("selected")) +'"]').addClass('selected');
  3574. }
  3575. let $selected = $('tr.selected');
  3576. if ( $selected.length === 0 ) { removeSearchParam('selected'); } // remove selected searchParam if item not found
  3577. switch(true) {
  3578. case $selected.length === 1 && $selected.hasClass('local'):
  3579. case window.self !== window.top: // do nothing for iframes
  3580. break;
  3581. case getSearchParams().has('file'): {// load files (from bookmark or url)
  3582. let $file = $('td.name span:contains("'+ decodeURIComponentSafe(getSearchParam('file')) +'")');
  3583. clickRow( $file.closest('tr').attr('id') );
  3584. removeSearchParam('file');
  3585. }
  3586. break;
  3587. case $('body').hasClass('has_audio') && getSearchParam('autoload_media') === 'true': // load audio; must be above next case
  3588. // showAudio( $('.audio').first().attr('id'),'', 'true' ); // load audio
  3589. clickRow( $('.audio').first().attr('id') ); // load audio
  3590. autoLoadCoverArt(); // load image
  3591. break;
  3592. case $('body').hasClass('has_video') && getSearchParam('autoload_media') === 'true': clickRow( firstRowID('.video') ); break; // load video (if no audio)
  3593. case $('body').hasClass('has_playlist'): case $('body').hasClass('has_filelist'): break; // do nothing for playlists and filelists ... but what about autoload media?
  3594. case $selected.length === 1: showContent($selected.attr('id')); break;
  3595. }
  3596. if ( $selected.length === 1 ) { clickRow( $selected.attr('id')); } // click selected history item --> replaces auto-loaded cover art
  3597. if ( getSearchParam('autoload_index_files') !== 'false' && $('tr.file.htm').find( 'a[href*="/index."]').length > 0 ) { // else load index file
  3598. clickRow($('tr.file.htm').find('a[href*="/index."]').closest('tr').attr('id'));
  3599. }
  3600. if ( $('tr.selected').length === 1 ) { scrollThis('tbody','selected'); }
  3601. }
  3602. // get cover art names
  3603. function getImageNames() {
  3604. let images = $dir_list_body.find('.image'), image_names = [], image_id, image_name;
  3605. for ( let i = 0; i < images.length; i++ ) {
  3606. image_id = images.eq(i).attr('id');
  3607. image_name = images.eq(i).find('.name').attr('data-name');
  3608. image_name = image_name.slice(0,image_name.lastIndexOf('.') ); // remove extension
  3609. image_names.push({'id':image_id,'name':image_name}); // add id and name
  3610. }
  3611. return image_names;
  3612. }
  3613. // get cover art id
  3614. function getCoverArtID() {
  3615. const cover_names = ['cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes'];
  3616. const image_names = getImageNames();
  3617. let id, exact_match, match, cover_name;
  3618. for ( cover_name of cover_names ) { // test available image names against cover names
  3619. exact_match = image_names.filter( el => el.name === cover_name ); // check for exact match (w/o extension)
  3620. match = image_names.filter( el => el.name.indexOf(cover_name) > -1 ); // check for partial match
  3621. if ( exact_match.length > 0 ) { return id = exact_match[0].id; }
  3622. else if ( match.length > 0 ) { return id = match[0].id; }
  3623. }
  3624. if (id === undefined ) { return id = image_names[0].id; } // if no matches, return first image id
  3625. }
  3626. // Autoload Cover Art
  3627. function autoLoadCoverArt() { // autoload cover art for audio if dir contains audio and images, and audio is loaded and non-image content is not loaded
  3628. if ( !$('body').hasClass('has_images') ) { return; } // do nothing if no images found
  3629. let cover_ID = getCoverArtID();
  3630. if ( cover_ID !== undefined ) {
  3631. let row = getElById(cover_ID);
  3632. row.addClass('content_loaded');
  3633. $content_pane.attr('data-content','has_image').find('#content_image').addClass('has_content').attr('src',row.find('a').attr('href'));
  3634. $('#title span').empty().html(decodeURIComponentSafe(row.find('.name a span').text() ) ); // name
  3635. setImageDimensions();
  3636. }
  3637. let selected_ID = ( getSearchParam('selected').length > 0 ? 'rowid-'+ getSearchParam('selected') : undefined);
  3638. if ( selected_ID !== undefined ) { document.getElementById(selected_ID).classList.add('selected'); $('.media').removeClass('selected'); }
  3639.  
  3640. }
  3641. //***** KEYBOARD EVENTS *****//
  3642. function metaKey(e) { return ( navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey); }
  3643. function cmdKey(e) { return ( metaKey(e) && !e.altKey && !e.shiftKey ); }
  3644. function cmdAltKey(e) { return ( metaKey(e) && e.altKey && !e.shiftKey ); }
  3645. function cmdShiftKey(e) { return ( metaKey(e) && !e.altKey && e.shiftKey ); }
  3646.  
  3647. $('#top, #iframe_body').on('keydown', function(e) {
  3648. const $selected = ( $('#content_pane').attr('data-content') === 'has_font_file' ? $('.glyph_container.selected .glyph') : $('#tbody').find('.selected') );
  3649. switch(true) { // Disable all keydown events except escape, return, and tab when help or warning is shown, or when textarea is focused
  3650. case document.activeElement.hasAttribute('contentEditable') && !/escape|tab/.test(e.key.toLowerCase()) && !( cmdKey(e) && e.key === 'w'):
  3651. case ( /button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ) && !/escape|tab|shiftkey|metakey|altkey/.test(e.key.toLowerCase()) ):
  3652. case ($('body').hasClass('has_warning') || $('body').hasClass('has_help') ) && !( cmdKey(e) || metaKey(e) || (/escape|tab|shiftkey|enter/.test(e.key.toLowerCase()) ) ):
  3653. return;
  3654. }
  3655. // MAIN KEYDOWN EVENTS
  3656. switch ( e.key ) {
  3657. case 'shiftKey':
  3658. switch(true) {
  3659. case $('body').hasClass('has_warning') || $('body').hasClass('has_help'):
  3660. if (e.key !== 'Enter' && e.key !== 'Tab') { e.preventDefault(); return false; }
  3661. break;
  3662. }
  3663. break;
  3664. case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight':
  3665. switch(true) {
  3666. case $('tr.selected').hasClass('link') && cmdKey(e) && e.key === 'ArrowDown': // webloc or url file
  3667. case $('tr.selected').hasClass('playlist') && cmdKey(e) && e.key === 'ArrowDown': // webloc or url file
  3668. $('tr.selected').dblclick();
  3669. break;
  3670. default: showWarning( 'arrowNavigation',e ); // arrow navigation
  3671. }
  3672. break;
  3673. case ' ': // space
  3674. switch(true) {
  3675. case $('#content_pane').hasClass('has_audio') || $('#content_pane').attr('data-content') === 'has_video': // Play/pause media (space bar)
  3676. e.preventDefault(); e.stopPropagation(); playPauseMedia(); break;
  3677. case window.top !== window.self && $('tr.audio_loaded').length === 1:
  3678. e.preventDefault(); e.stopPropagation(); sendMessage('top','iframe_play_pause_media'); break;
  3679. default: alphaNav(e);
  3680. }
  3681. break;
  3682. case 'Enter': // Open directories (or ignored)
  3683. switch(true) {
  3684. case $('body').hasClass('has_warning') || $('body').hasClass('has_help'):
  3685. e.preventDefault();
  3686. $('button.focus, button:focus').click(); // click focused warning button in #top or #iframe
  3687. break;
  3688. case window.self !== window.top: // if iframe...
  3689. switch(true) {
  3690. case $('body').hasClass('has_menu'):
  3691. sendMessage('top','clickMenu'); // close main menu
  3692. break;
  3693. case $('tr.selected').hasClass('dir') && cmdKey(e):
  3694. case $('tr.selected').hasClass('file') && cmdKey(e): // open iframe dir or file
  3695. case $('tr.selected').hasClass('link') && cmdKey(e): // webloc or url file
  3696. $('tr.selected').find('a').trigger('dblclick');
  3697. break;
  3698. case !$('.audio.selected').hasClass('audio_loaded'):
  3699. $('.audio.selected').find('a').trigger('dblclick');
  3700. break;
  3701. case $('.audio_loaded').length === 1 && !$('.selected').hasClass('audio_loaded'): // play/pause media
  3702. e.preventDefault(); e.stopPropagation(); playPauseMedia();
  3703. break;
  3704. }
  3705. break;
  3706. case $('body').hasClass('has_menu'):
  3707. e.preventDefault(); clickMenu(); sendMessage('iframe','close_menu'); // click selected menu item
  3708. break;
  3709. case $selected.hasClass('app') && $settings.apps_as_dirs === false: // don't open app folders
  3710. break;
  3711. default:
  3712. switch(true) {
  3713. case $selected.hasClass('.disabled'):
  3714. case $content_pane.attr('data-content') === 'has_text_editor':
  3715. break;
  3716. case $selected.hasClass('audio') && !$selected.hasClass('audio_loaded'): // load selected audio file
  3717. showAudio($('.audio.selected').attr('id'));
  3718. break;
  3719. case $selected.hasClass('media'): // else play/pause playing media
  3720. e.preventDefault(); e.stopPropagation(); playPauseMedia();
  3721. break;
  3722. case $selected.hasClass('link'):
  3723. case $selected.hasClass('dir'): // else open dir
  3724. case $('tr.selected').hasClass('link') && cmdKey(e): // webloc or url file
  3725. case $('tr.selected').hasClass('playlist') && cmdKey(e): // webloc or url file
  3726. $selected.find('a').trigger('dblclick');
  3727. break;
  3728. default: $selected.click();
  3729. }
  3730. }
  3731. break;
  3732. // Alphabetical navigation
  3733. case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'X': case 'Y': case 'Z':
  3734. case 'a': case 'b': case 'c': case 'f': case 'h': case 'j': case 'k': case 'l': case 'm': case 'n': case 'p': case 'q': case 's': case 't': case 'u': case 'v': case 'x': case 'y': case 'z':
  3735. case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '~': case '!': case '@': case '#': case '$': case '%': case '^': case '&': case '*': case '(': case ')': case '`': case '+': case '[': case ']': case '{': case '}': /*case '\\':*/ case '|': case '/': case ',': case '?': case '–': case '—': case '\'': case '"': case '“': case '”': case '‘': case '’': case '…': case 'π':
  3736. case '∫': case '∂': case 'ƒ': case '©': case '˙': case '∆': case '˚': case 'µ': case '®': case 'ß': case '†': case '√': case '∑': case '≈': case '¥': case 'Ω': case '¡': case '™': case '£': case '¢': case '∞': case '§': case '¶': case '•': case 'ª': case 'º': case '≠':
  3737. case '⁄': case '€': case '‹': case '›': case 'fl': case '‡': case '°': case '·': case '‚': case '±': case '¯': case '˘': case '¿':
  3738. case 'ı': case '': case '´': case '˝': case 'ˆ': case '': case '˜': case '‰': case 'ˇ': case '¨': case '◊': case '„': case '˛': case '¸':
  3739. // case 'α': case 'β': case 'γ': case 'δ': case 'ε': case 'ζ': case 'η': case 'θ': case 'ι': case 'κ': case 'λ': case 'μ': case 'ν': case 'ξ': case 'ο': case 'π': case 'ρ': case 'ς': case 'σ': case 'τ': case 'υ': case 'φ': case 'χ': case 'ψ': case 'ω': case 'ϑ': case 'ϒ': case 'ϖ':
  3740. // case 'Α': case 'Β': case 'Γ': case 'Δ': case 'Ε': case 'Ζ': case 'Η': case 'Θ': case 'Ι': case 'Κ': case 'Λ': case 'Μ': case 'Ν': case 'Ξ': case 'Ο': case 'Π': case 'Ρ': case 'Σ': case 'Σ': case 'Τ': case 'Υ': case 'Φ': case 'Χ': case 'Ψ': case 'Ω': case 'Θ': case 'ϒ':
  3741. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) { alphaNav(e); }
  3742. break;
  3743. case 'd': // Cmd/Ctrl + D: Toggle Details
  3744. if ( cmdKey(e) && !$body.hasClass('has_warning') ) { e.preventDefault(); $('#show_details').click(); } else { alphaNav(e); }
  3745. break;
  3746. case 'e': // Cmd/Ctrl + E: Toggle Main Menu or Text Editor
  3747. switch(true) {
  3748. case $body.hasClass('has_warning'): break;
  3749. case cmdShiftKey(e): // toggle text editor
  3750. e.preventDefault();
  3751. if ( window.self !== window.top ) { sendMessage('top','toggle_text_editor'); } else { $('#text_editor_row').find('a').click(); }
  3752. $('body').addClass('faded');
  3753. break;
  3754. case cmdKey(e): // toggle main menu
  3755. e.preventDefault();
  3756. if ( window.self !== window.top ) { sendMessage('top','toggle_menu'); } else { $('#menu_container').click(); }
  3757. break;
  3758. default: alphaNav(e);
  3759. }
  3760. break;
  3761. case 'g': // Cmd/Ctrl + G: Show image Grid
  3762. if ( cmdKey(e) && ( $('#top').hasClass('has_images') || $('#top').hasClass('has_fonts') ) ) { e.preventDefault(); $('#grid_btn').click(); } else { alphaNav(e); }
  3763. break;
  3764. case 'i': // Cmd/Ctrl + I: Toggle Invisibles
  3765. switch(true) {
  3766. case cmdKey(e):
  3767. e.preventDefault();
  3768. if ( window.self !== window.top ) { sendMessage('top','toggle_invisibles'); } else { $('#show_invisibles_container').find('input').click(); }
  3769. break;
  3770. default: alphaNav(e);
  3771. }
  3772. break;
  3773. case 'o': // Cmd/Ctrl + Shift + O: Open selected item in new window
  3774. if ( cmdShiftKey(e) ) { window.open( $('#tbody').find('.selected').find('a').attr('href') ); } else { alphaNav(e); }
  3775. break;
  3776. case 'r': // Cmd/Ctrl + Shift + R: Refresh
  3777. switch(true) {
  3778. case cmdKey(e) && window.top !== window.self: e.preventDefault(); sendMessage('top','reload'); break; // send reload message to top
  3779. case cmdKey(e): e.preventDefault(); $('#reload_btn').click(); break; // click reload/reset button
  3780. default: alphaNav(e);
  3781. }
  3782. break;
  3783. case 'w': // Cmd/Crtl + W: if content pane has content, click close button; otherwise default behavior (close tab/window); Firefox does not allow scripts to override this behavior
  3784. switch(true) {
  3785. case cmdKey(e):
  3786. switch(true) {
  3787. case window.top !== window.self:
  3788. e.preventDefault();
  3789. sendMessage('top','close'); // send close message to top
  3790. break;
  3791. case $content_pane.hasClass('has_audio'):
  3792. case $body.hasClass('has_playlist'):
  3793. case $body.hasClass('has_filelist'):
  3794. case $body.hasClass('iframe_edited'): // close edited iframe file
  3795. case $content_pane.attr('data-content') !== undefined:
  3796. e.preventDefault();
  3797. $('#close_btn').click(); // click close button
  3798. break;
  3799. default: return true; // else close window (or normal behavior)
  3800. }
  3801. break;
  3802. default: alphaNav(e);
  3803. }
  3804. break;
  3805. case '=': // equals sign
  3806. switch(true) {
  3807. case cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test($content_pane.attr('data-content')): e.preventDefault(); $('#increase').click(); break;
  3808. case cmdKey: return; // allow normal Cmd + behavior
  3809. default: alphaNav(e);
  3810. }
  3811. break;
  3812. case '-': // hyphen
  3813. switch(true) {
  3814. case cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test($content_pane.attr('data-content')): e.preventDefault(); $('#decrease').click(); break;
  3815. case cmdKey: return; // allow normal Cmd - behavior
  3816. default: alphaNav(e);
  3817. }
  3818. break;
  3819. case '\\': // \ backslash
  3820. switch(true) {
  3821. case cmdShiftKey(e): // Cmd Shift + \ : toggle split
  3822. switch(true) {
  3823. case window.top === window.self && $content_pane.attr('data-content') === 'has_iframe': sendMessage('iframe','split_view'); break; // send toggle split message to top
  3824. case $('#split_view').height() > 0: $('#split_view').click(); break; // if split view visible...click toggle split
  3825. }
  3826. break;
  3827. case cmdKey(e): // Cmd + \ : toggle sidebar
  3828. if ( window.top !== window.self ) { sendMessage('top','hide_sidebar'); } else { $('#hide_sidebar').click(); }
  3829. break;
  3830. }
  3831. break;
  3832. case 'Tab':
  3833. e.preventDefault();
  3834. switch(true) {
  3835. case $('body').hasClass('has_warning'):
  3836. tabWarningButtons(e); break; // focus warning buttons
  3837. case document.activeElement.id === 'content_image':
  3838. focusSidebar(); break; // focus sidebar from image
  3839. case $content_pane.attr('data-content') === 'has_text_editor':
  3840. focusTextEditorPanes(); break; // focus text editor
  3841. case window.top !== window.self && /\.html|\.htm/.test(window.location.pathname) && $('a,button,input,select,textarea').length > 0:
  3842. focusFocusableEls(e); break;
  3843. case (/has_markdown|has_text|has_code|has_htm/.test($content_pane.attr('data-content')) && window.top === window.self ):
  3844. $('body').addClass('focus_content'); $content_iframe.focus(); sendMessage('iframe','focus_iframe'); break; // focus iframe text editor or htm files from top
  3845. case ( /text_preview|html_preview|text_source/.test(document.activeElement.id) ):
  3846. toggleTextEditorPanes(); break; // toggle focused text editor panes
  3847. case $(document.activeElement).closest('#text_preview').length === 1:
  3848. focusFocusableEls(e,'#text_preview'); break; // focus focusable elements in text preview
  3849. case $(document.activeElement).closest('#font_specimen').length === 1:
  3850. focusFocusableEls(e,'#font_specimen'); break; // focus focusable elements in font specimen
  3851. case document.activeElement.hasAttribute('contenteditable'):
  3852. case window.top !== window.self: // If iframe is focused, focus sidebar, dim selected iframe dir list item
  3853. e.preventDefault();
  3854. switch(true) {
  3855. case $('#iframe_dir_list_wrapper').length === 1:
  3856. case !/a|button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ): // allow form elements to be focused
  3857. $('#iframe_body,#dir_list tr.selected').addClass('is_blurred'); // dim iframe dir_list
  3858. sendMessage('top','tab'); // focus sidebar
  3859. break;
  3860. case $('a,button,input,select,textarea').length > 0: // allow tabbing to form elements and textareas (e.g., in previewed html pages)
  3861. if ( $('body').hasClass('has_text_editor_UI') ) { focusFocusableEls(e,'#text_container'); } else { focusFocusableEls(e); }
  3862. break;
  3863. }
  3864. break;
  3865. case window.top === window.self: // Either focus sidebar or refocus content pane (after clicking menu, for example)
  3866. switch(true) {
  3867. case $('body').hasClass('focus_content') && $('body').hasClass('has_menu'): case !$('body').hasClass('focus_content'): focusContent('',e); break;
  3868. case $('body').hasClass('focus_content'): case $('body').hasClass('has_menu'): focusSidebar(); break;
  3869. }
  3870. break;
  3871. }
  3872. break;
  3873. case 'Escape': // remove text selections, close warnings or help, close menus (and refocus content), or focus sidebar.
  3874. if ( $content_pane.attr('data-loaded') !== 'loaded' ) { $content_pane.removeAttr('data-loaded'); $content_iframe.removeAttr('src'); $('#font_styles').empty(); } // close loading iframe
  3875. if ( window.top !== window.self ) { $('#iframe_body').addClass('is_blurred'); sendMessage('top','escape'); } else { focusSidebar(); }
  3876. window.getSelection().removeAllRanges();
  3877. $('#content_playlist, #content_audio_playlist').removeClass('has_content');
  3878. $('#warning_btn_cancel,#close_help').click();
  3879. $(document).off('mousemove');
  3880. $('body').removeClass('has_overlay');
  3881. break;
  3882. case '.':
  3883. switch(true) {
  3884. case cmdKey(e):
  3885. if ( $('body').hasClass('has_warning') ) {
  3886. e.preventDefault();
  3887. $('#warning_btn_cancel,#close_help').click(); // click cancel buttons
  3888. }
  3889. break;
  3890. }
  3891. } // end switch
  3892. });
  3893. // ***** END KEYBOARD EVENTS ***** //
  3894.  
  3895. // ***** GRID SETUP ***** //
  3896. // Create Font Grid Items
  3897. function fontGridItems() {
  3898. let $font_grid_items_arr = [], $font_files = $dir_list_body.find('.font'), new_grid_item;
  3899. $('#font_grid_styles').empty();
  3900. for ( let i = $font_files.length; i--; ) {
  3901. new_grid_item = setContentFontSource( $font_files.eq(i).attr('id'), true, 'font_grid' );
  3902. $font_grid_items_arr.unshift( new_grid_item );
  3903. }
  3904. $font_grid_items_arr[$font_grid_items_arr.length - 1].addClass('border_bottom_x'); // add bottom border to last
  3905. return $font_grid_items_arr;
  3906. }
  3907. // Create Image Grid Items
  3908. function imageGridItems() {
  3909. let $image_grid_items_arr = [], $image_files = $dir_list_body.find('.image:not(.ignored)'), classes = 'image_grid_item border_right_x border_bottom_x', image_files_length = $image_files.length;
  3910. for ( let i = image_files_length; i--; ) {
  3911. const id = $image_files.eq(i).attr('id');
  3912. const this_link = $image_files.eq(i).find('a').attr('href');
  3913. const exts = $item_kind.image.filter( ext => $.inArray(ext, $row_settings.ignored) == -1 ); // decide which image files can be displayed
  3914. const title_name = this_link.slice(this_link.lastIndexOf('/') + 1);
  3915. if ( $.inArray( $image_files.eq(i).attr('data-ext'), exts ) > -1 ) { // if this row file ext is in the image extension array
  3916. let item = `<div class="${ classes } background_color_EE_22" data-ID="${ id }" data-index="${ i }"><a href="${ this_link }"><img src="${ this_link }" title="${ title_name }" loading="lazy" /></a></div>`;
  3917. $image_grid_items_arr.unshift( item );
  3918. }
  3919. }
  3920. return $image_grid_items_arr;
  3921. }
  3922. // Make Grids
  3923. function makeGrids(id) {
  3924. closeGrid(); // remove previous grid items
  3925. $content_pane.removeClass('has_hidden_grid has_image_grid has_font_grid'); // reset content_pane grid classes
  3926. switch(true) {
  3927. case id === 'show_font_grid' || !$body.hasClass('has_images'): $content_pane.addClass('has_font_grid'); $content_grid.append( fontGridItems() ); break; // only show font grid
  3928. case id === 'show_image_grid' || !$body.hasClass('has_fonts'): $content_pane.addClass('has_image_grid'); $content_grid.append( imageGridItems() ); break; // only show image grid
  3929. default: $content_grid.append( imageGridItems(), fontGridItems() ); // show grid of both images and fonts
  3930. }
  3931. }
  3932. // Click grid button: make grid and show grid
  3933. $('#sidebar_header').on('click', '#grid_btn, #show_font_grid, #show_image_grid', function(e) { e.stopPropagation(); showContent($(this).attr('id')); });
  3934.  
  3935. // ***** SCALE PREVIEWED IMAGES & FONTS ***** //
  3936. // Scale Fonts
  3937. function scaleFonts(e, incr, id) {
  3938. const font_size = parseInt(getComputedStyle(document.body).fontSize); // pts/em
  3939. const getFontSize = function(el) { return parseFloat(el.css('font-size')); };
  3940. if ( id === 'decrease' ) { incr = 1/incr; }
  3941. if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_grid.css({'font-size':( getFontSize($content_grid)/font_size * incr ) +'em'}); return; }
  3942. if ( $content_pane.attr('data-content') === 'has_font' ) { $content_font.css({'font-size':( getFontSize($content_font)/font_size * incr ) +'em'}); return; }
  3943. scrollThis('content_container','content_font');
  3944. }
  3945. // Scale Glyphs
  3946. function scaleGlyphs(e, incr, id) {
  3947. if ( id === 'decrease' ) { incr = 1/incr; }
  3948. let scale = ( $('#glyph_viewer').attr('data-scale') === undefined ? incr : incr * $('#glyph_viewer').attr('data-scale') );
  3949. if ( scale >= 1 ) {
  3950. $('#glyph_viewer').css({'width': scale * 100 +'%','height': scale * 100 +'%'});
  3951. document.getElementById('content_font').scrollLeft = -( Math.round( $('#font_viewer').outerWidth(true) - $('#glyph_viewer').width() ) / 2 );
  3952. document.getElementById('content_font').scrollTop = -( Math.round( $('#font_viewer').outerHeight(true) - $('#glyph_viewer').height() ) / 2 );
  3953. }
  3954. $('#glyph_viewer').css({'background-size': scale * 100 +'%'}).attr('data-scale',scale.toFixed(2));
  3955. }
  3956. // Scale Image Grid items
  3957. function scaleGridImages(incr,id) {
  3958. if ( id === 'decrease' ) { incr = 1/incr; }
  3959. const $image_grid_item = $('.image_grid_item img');
  3960. let image_grid_item_width = Number.parseFloat( $image_grid_item.width(),10) * incr;
  3961. let image_grid_item_height = Number.parseFloat( $image_grid_item.height(),10) * incr;
  3962. let image_grid_item_max_width = Number.parseFloat( $image_grid_item.css('maxWidth'),10) * incr;
  3963. let image_grid_item_max_height = Number.parseFloat( $image_grid_item.css('maxHeight'),10) * incr;
  3964. // prevent reducing grid image size on first scale click:
  3965. if ( image_grid_item_width < image_grid_item_max_width ) { image_grid_item_width = image_grid_item_max_width; }
  3966. if ( image_grid_item_height < image_grid_item_max_height ) { image_grid_item_height = image_grid_item_max_height; }
  3967. // set grid properties
  3968. $content_grid.css({'grid-template-columns':'repeat(auto-fill, minmax('+ (image_grid_item_width +16) +'px, auto ) )'});
  3969. $content_grid.find('img').css({'max-width':( image_grid_item_width ) +'px', 'max-height':( image_grid_item_height ) +'px'});
  3970. return;
  3971. }
  3972. // Zoom Images on click
  3973. function scaleImages(e,incr,id) {
  3974. const $content_container = ( $('#iframe_body > img').length === 1 ? $('#iframe_body') : $('#content_container') );
  3975. const $this_image = ( $('#iframe_body > img').length === 1 ? $('#iframe_body > img') : $content_image );
  3976. const this_link = $this_image.attr('src');
  3977. let CC_width = function() { return Math.round($content_container.width()); };
  3978. let CC_height = function() { return Math.round($content_container.height()); };
  3979. const this_width = Math.round($this_image.width());
  3980. const this_height = Math.round($this_image.height());
  3981. const iframe_delta = ( $('#iframe_body > img').length === 1 ? Number.parseInt($('#iframe_body').css('padding')) : 0 );
  3982. // scale grid images
  3983. if ( $content_pane.attr('data-content') === 'has_grid' ) {
  3984. scaleGridImages(incr,id);
  3985. } else { // scale single images
  3986. getImageDimensions( this_link, function( width, height ) {
  3987. switch(true) {
  3988. case incr !== undefined && id !== undefined: // scale images by increment
  3989. $content_pane.addClass('has_scaled_image').removeClass('has_zoom_image'); // remove zoom classes in case window resized after zoom
  3990. if ( id === 'increase' ) {// && this_width < width && this_height < height) {
  3991. $this_image.css({'width':this_width * incr, 'height': this_height * incr});
  3992. $this_image.css({'width':this_width * incr, 'height': 'auto', 'max-width':'none', 'max-height':'none' });
  3993. }
  3994. if ( id === 'decrease' && ( this_width >= 1 && this_height >= 1 ) ) {
  3995. $this_image.css({'width':this_width / incr, 'height': 'auto', 'max-width':'none', 'max-height':'none' });
  3996. }
  3997. // keep images centered when scaling
  3998. if ( Math.round($this_image.width()) >= CC_width() ) {
  3999. $content_image_container.scrollLeft( ( Math.round( ( $this_image.outerWidth(true) ) - CC_width() ) )/2 ) ;
  4000. }
  4001. if ( Math.round($this_image.height()) <= CC_height() ) {
  4002. $content_image_container.scrollTop( ( CC_height() - Math.round( $this_image.height() ) )/2 );
  4003. } else {
  4004. $content_image_container.scrollTop( ( Math.round($this_image.outerHeight(true)) - CC_height())/2 ) ;
  4005. }
  4006. break;
  4007. default: { // else zoom single image on click
  4008. $this_image.removeAttr('style');
  4009. if ( width <= CC_width() && height <= CC_height() ) { // but don't zoom small images:
  4010. $content_pane.removeClass('has_zoom_image has_scaled_image');
  4011. return;
  4012. } // otherwise, zoom image:
  4013. const $CC_offset = $content_container.offset();
  4014. const $img_offset = $this_image.offset();
  4015. // x,y coordinates of zoom click as percentage of image width/height
  4016. const percentX = (e.pageX - $img_offset.left)/this_width;
  4017. const percentY = (e.pageY - $img_offset.top)/this_height;
  4018. // calculate scroll by pixel coordinates of full-size image - click coordinates and content_container offsets
  4019. const scrollX = (width * percentX) - e.pageX + $CC_offset.left - (iframe_delta * width / this_width);
  4020. const scrollY = (height * percentY) - e.pageY + $CC_offset.top - (iframe_delta * height / this_height);
  4021.  
  4022. $content_pane.removeClass('has_scaled_image').toggleClass('has_zoom_image' );
  4023. $content_image_container.scrollLeft( scrollX );
  4024. $content_image_container.scrollTop( scrollY );
  4025. }
  4026. }
  4027. });
  4028. setImageDimensions();
  4029. }
  4030. }
  4031. // Zoom single image on click
  4032. $content_image_container.on('click','img', function(e) { scaleImages(e); focusContent('content_image_container'); });
  4033. // Scale Fonts and Images
  4034. function scalePreviewItems(e,id) { // combine scaling into one function
  4035. if ( $content_pane.attr('data-content') === 'has_glyph' ) { scaleGlyphs(e, 1.125, id); return; }
  4036. scaleImages(e, 1.125, id );
  4037. scaleFonts(e, 1.125, id );
  4038. }
  4039. // Scale Buttons
  4040. $('#scale').on('click','span', function(e) {
  4041. e.preventDefault(); e.stopPropagation();
  4042. scalePreviewItems(e, $(this).attr('id') );
  4043. $('#reload_btn').addClass('reset');
  4044. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  4045. });
  4046. // ***** END SCALE PREVIEW ITEMS ***** //
  4047.  
  4048. // ***** AUDIO CONTENT ***** //
  4049. // Update Playlist
  4050. function updateTrackList() {
  4051. let playlist = [];
  4052. $dir_list_body.find('.media').not('.unchecked').not('.disabled').not('.audio_loaded').not('.content_loaded').not('.selected').each(function() { playlist.push( $(this).attr('id') ); });
  4053. return playlist;
  4054. }
  4055. // Randomize Shuffle List
  4056. function shuffleArray(array) {
  4057. 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]]; }
  4058. return array;
  4059. }
  4060. // Attach Shuffle List data to $audio_player
  4061. function updateShuffleList(id) {
  4062. let shuffle_list;
  4063. switch(true) {
  4064. case !$body.hasClass('shuffle_media'):
  4065. break;
  4066. case id !== undefined: // don't include .playing and .unchecked track in shufflelist
  4067. shuffle_list = $audio_player.data('shufflelist');
  4068. switch(true) {
  4069. case $(document.getElementById(id)).hasClass('unchecked') || ( /audio_loaded|content_loaded/.test(document.getElementById(id).classList.value) ):
  4070. shuffle_list.splice(shuffle_list.indexOf(id), 1); $audio_player.data('shufflelist',shuffle_list); break;
  4071. default:
  4072. shuffle_list.push(id); shuffle_list = shuffleArray( shuffle_list );
  4073. }
  4074. break;
  4075. default:
  4076. shuffle_list = shuffleArray( updateTrackList() );
  4077. $audio_player.data('shufflelist',shuffle_list);
  4078. }
  4079. }
  4080. // Click media checkboxes
  4081. $('#tbody').on('click','tr.media input', function(e) {
  4082. if (getBrowser() === 'is_gecko') { e.preventDefault(); } // because Firefox sucks
  4083. e.stopPropagation(); toggleChecked(this);
  4084. });
  4085. // Toggle single media checkboxes and update shufflelist
  4086. function toggleChecked(el) { el.blur(); $(el).closest('tr').toggleClass('unchecked'); updateShuffleList($(el).closest('tr').attr('id')); }
  4087. // toggle all media checkboxes and update shufflelist
  4088. function toggleAllChecked() { $dir_list_body.find('> tr').find('input').trigger('click'); updateShuffleList(); }
  4089. $('#play_toggle').on('click',function(e) { e.preventDefault(); e.stopPropagation(); toggleAllChecked(); });
  4090. // Is Playing; returns true if all conditions are true
  4091. function isPlaying(id) {
  4092. switch(true) {
  4093. case window.top !== window.self:
  4094. return ( $('body').hasClass('is_playing') ? true : false );
  4095. default: return ( id !== undefined && document.getElementById(id).currentTime > 0 && !document.getElementById(id).paused && !document.getElementById(id).ended );
  4096. }
  4097. }
  4098. // Play Media
  4099. function playMedia(task) {
  4100. if ( $content_pane.hasClass('has_audio') ) { $audio_player.trigger(task); } else { $content_video.trigger(task); }
  4101. setPlayerStatus(task);
  4102. }
  4103. function setPlayerStatus(task) {
  4104. if ( task === 'play' ) { $('body').removeClass('is_paused').addClass('is_playing'); } else { $('body').removeClass('is_playing').addClass('is_paused'); }
  4105. if ( $content_pane.hasClass('has_iframe_audio') ) { sendMessage('iframe','setIframePlayerStatus','',task); }
  4106. }
  4107. // Skip media tracks +/-10/30 seconds
  4108. function mediaSkip(e,args) {
  4109. let factor, skip;
  4110. switch(true) {
  4111. case e !== undefined: factor = ( e.key === 'ArrowLeft' ? -1 : 1 ); skip = ( e.altKey && e.shiftKey ? 30 : e.altKey ? 10 : null ); break; // from top
  4112. case args !== undefined: factor = ( args[0] === 'ArrowLeft' ? -1 : 1 ); skip = args[1] || 0; break; // from iframe
  4113. }
  4114. const $player = ( $('#content_pane').hasClass('has_audio') ? $audio_player : $content_video ); // audio or video?
  4115. const time = $player.prop('currentTime'); // current time
  4116. $player.prop('currentTime', time + factor*(skip)); // set time
  4117. }
  4118. // Play/Pause Audio/Video
  4119. function playPauseMedia() {
  4120. let playerID = ( $content_pane.attr('data-content') === 'has_video' ? 'content_video' : 'audio' );
  4121. let player = document.getElementById( playerID );
  4122. switch(true) {
  4123. case isPlaying( playerID ): player.pause(); $('body').removeClass('is_playing').addClass('is_paused'); break;
  4124. default: player.play(); $('body').removeClass('is_paused').addClass('is_playing');
  4125. }
  4126. }
  4127. // Toggle Audio Playback Options (shuffle, loop)
  4128. function audioPlaybackOptions(id) {
  4129. switch(true) {
  4130. case id === 'shuffle': case id === 'shuffle_media_files': $body.toggleClass('shuffle_media'); updateShuffleList(); break;
  4131. case id === 'loop': case id === 'loop_media_files': $body.toggleClass('loop_media'); break;
  4132. }
  4133. switch(true) { // change audio checkboxes prop
  4134. case id === 'shuffle_media_files': ( $('#shuffle').prop('checked') === true ? $('#shuffle').prop({'checked':false}) : $('#shuffle').prop({'checked':true}) ); break;
  4135. case id === 'loop_media_files': ( $('#loop').prop('checked') === true ? $('#loop').prop({'checked':false}) : $('#loop').prop({'checked':true}) ); break;
  4136. }
  4137. }
  4138. // click loop or shuffle audio options
  4139. $('#media_files_menu').on('click','#loop_media_files,#shuffle_media_files', function() { audioPlaybackOptions( $(this).attr('id') ); closeMenus(); });
  4140. $('#audio_options').on('click','input', function() { audioPlaybackOptions( $(this).attr('id') ); $(this).blur(); document.getElementById('top').focus(); });
  4141. // Initialize Media: play next track on ended and scroll to playing item
  4142. function initMedia() {
  4143. $('#audio,#content_video').on('ended', function() {
  4144. switch(true) {
  4145. // play track from iframe
  4146. case $content_pane.hasClass('has_iframe_audio'): sendMessage('iframe','play_prev_next_iframe_audio','','ArrowRight'); break;
  4147. case $content_pane.hasClass('has_audio'): playPrevNextMediaTrack('ArrowRight','true'); break;
  4148. case $content_pane.attr('data-content') === 'has_video': playPrevNextMediaTrack('ArrowRight','true'); break;
  4149. }
  4150. scrollThis('tbody','.audio_loaded');
  4151. scrollThis('tbody','.video.content_loaded');
  4152. });
  4153. }
  4154. // ***** END AUDIO PLAYBACK ***** //
  4155. // ***** IFRAME SETUP ***** //
  4156. function setUpIframeUI() { // set up iframe directory UI or iframe text editor
  4157. let iframe_location = decodeURIComponentSafe(window.location.href), content = '';
  4158. $('body').attr('id','iframe_body'); // add iframe body id
  4159. $('video').css({'width':'100%'});
  4160. const text_files = $item_kind.markdown.concat($item_kind.text, $item_kind.code); // define which files are editable
  4161. const link_files = $item_kind.link;
  4162. switch(true) {
  4163. case window.location.pathname.indexOf('/?') > 0: // set up iframe directory
  4164. case window.location.pathname.endsWith('/'): // set up iframe directory
  4165. setUpIframeDirUI(iframe_location);
  4166. break;
  4167. case text_files.includes( window.location.pathname.slice( window.location.pathname.lastIndexOf('.') + 1 ) ): // else set up iframe text editor
  4168. setUpTextEditorUI();
  4169. if ( iframe_location.endsWith('.cue') ) { content = $('#text_source').val(); } // get cue sheet text for processing
  4170. if ( link_files.includes( iframe_location.slice(iframe_location.lastIndexOf('.') + 1 ) ) ) { content = $('#iframe_body').find('> pre').html(); } // get link file content for processing
  4171. break;
  4172. default:
  4173. // $('body').prepend(text_editing_UI_els);
  4174. break; // all other iframe content (e.g., html files)
  4175. }
  4176. let args = [iframe_location,content]; // args[0] = link, args[1] = iframe_content
  4177. sendMessage('top','iframe_loaded','',args);
  4178. }
  4179. // IFRAME DIRECTORY Prep
  4180. function setUpIframeDirUI(link) { // set up iframe dir_list UI and utility iframes (for subdirectories)
  4181. let parent_link = link.split('/').slice(0,-2).join('/') +'/';//+ window.location.search;
  4182. let query_str = new URLSearchParams(window.location.search.toString().slice(1));
  4183. let $iframe_body = $('#iframe_body'), $iframe_head = $('head'), $iframe_utility_iframe = '<iframe id="content_iframe_utility" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" style="display:none;"></iframe>';
  4184. let body_classes = []; // make array of classes to add all at once
  4185. if ( getBrowser() === 'is_gecko' ) { body_classes.push('is_gecko'); } // append gecko styles and fix links
  4186. switch(true) {
  4187. case query_str.get('view_directory_source') === 'true':
  4188. case query_str.get('is_error') === 'true':
  4189. break; // show raw directory index if error or viewing directory source
  4190. default: // else set up iframe directory
  4191. for ( let key of query_str.keys() ) {
  4192. switch(true) {
  4193. case query_str.get(key) === 'true': body_classes.push(key); break; // add body classes for boolean params
  4194. case query_str.get(key) !== 'false': body_classes.push(key+'_'+getSearchParam(key)); break; // non-boolean params (theme, sort, sort_direction)
  4195. }
  4196. }
  4197. $iframe_head.find('style, link[rel="stylesheet"], link[href$="css"]').remove(); // remove any existing directory index styles
  4198. $iframe_head.append('<style id="iframe_dir_styles">'+ $iframe_dir_styles +'</style><style id="color_and_background_styles">'+ $color_and_background_styles +'</style>');
  4199. $iframe_head.append('<style id="gecko_style_rules">'+ $gecko_style_rules +'</style>'); // no break;
  4200. // if opening a subdirectory, use dir_list_subdir to contain prepped index (i.e., we don't need the sidebar or other UI elements.
  4201. let dir_list_subdir = '<table id="dir_list"><tbody id="tbody" class="border_bottom text_color_111"></tbody></table>'; // dir_list_subdir
  4202. let iframe_table = ( query_str.get('subdirectory') === 'true' ? dir_list_subdir : ContentIframeDirEls(parent_link) ); // get iframe directory UI for regular iframe or subdirectory
  4203.  
  4204. const make_new_index = makeNewIndex($iframe_body, query_str.get('sort_by'))[0]; // make new index
  4205. const prepped_index = make_new_index[0]; // define prepped index
  4206. const additional_classes = make_new_index[1]; // additional body classes
  4207. const stats = make_new_index[2]; // stats
  4208. $iframe_body.removeAttr('style').addClass(additional_classes).addClass(body_classes.join(' ')).empty().append(iframe_table).find('#tbody').append(prepped_index);
  4209. $('#tfoot').append(stats); // add UI & prepped_index
  4210. if ( query_str.get('subdirectory') === null ) { $iframe_body.append($iframe_utility_iframe); } // append utility iframe only in top iframe; i.e., don't nest utility iframes
  4211. if ( query_str.get('subdirectory') === 'true' ) { sendMessage('top','dir_list_subdir_loaded','',$('#tbody').html() ); } // if content_iframe_utility, send #top prepped directory for subdirectories
  4212. }
  4213. }
  4214. // IFRAME Click
  4215. $('#iframe_body, #iframe_body #toolbar, #iframe_body #toolbar li, #iframe_body #tbody, #iframe_body .has_icon_before_before').on('click', function() {
  4216. $('body').removeClass('is_blurred'); sendMessage('top','iframe_click'); // tell top to close menus, focus content
  4217. });
  4218. // IFRAME Select iframe row on click or play/pause iframe audio
  4219. function iframeSelectRow(row) {
  4220. $('body').removeClass('is_blurred');
  4221. row.addClass('selected').removeClass('is_blurred').siblings('tr').removeClass('is_blurred selected'); // set selected classes
  4222. if ( row.hasClass('audio_loaded') ) { sendMessage('top','iframe_play_pause_media'); } // play/pause media
  4223. }
  4224. $('#iframe_body').on('click', function(e) { $(this).removeClass('has_stats'); });
  4225. $('#iframe_body').on('click','#tbody tr', function(e) {
  4226. e.preventDefault(); e.stopImmediatePropagation();
  4227. $('#iframe_body').removeClass('has_stats');
  4228. iframeSelectRow($(this));
  4229. sendMessage('top','iframe_click');
  4230. });
  4231. $('#iframe_body').on('click','textarea,form,select,input,option,output', function(e) {
  4232. e.preventDefault(); e.stopImmediatePropagation();
  4233. });
  4234. // IFRAME Click links from html files (should really combine with iframeDoubleClickRow())
  4235. function iframeClickLink(e,link,id) {
  4236. let url, kind;
  4237. if ( !link.startsWith('#') ) { e.preventDefault(); url = newURL(link); } // if link is not a link fragment
  4238. switch(true) {
  4239. case url === undefined: case link.startsWith('#'): break; // allow default link fragment behavior
  4240. case id === 'tbody': e.preventDefault(); window.location = link + '?&view_directory_source=true'; break; //
  4241. case id === 'parent': sendMessage('top','show_iframe_dir','',[link,'dir']); break; // open parent directory in iframe; add data-iframe_link to $content_iframe
  4242. 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
  4243. case url.protocol !== 'file:' && window.location.protocol === 'file:': window.open(link,'_blank'); break; // open remote link from local page in new tab/window
  4244. case url.protocol === 'file:' && window.location.protocol === 'file:': // no break; open local links on local files in iframe
  4245. case url.protocol === 'about:': // no break; document #link fragments
  4246. case RegExp(url.hostname).test(window.location.hostname): // no break; same origin links (might not include TLD) (just covering bases here)
  4247. case RegExp(window.location.hostname).test(url.hostname): // no break; same origin links (might not include TLD) (just covering bases here)
  4248. kind = getLinkInfo(url.href)[3];
  4249. if ( /dir|app/.test(kind) ) { sendMessage('top','show_iframe_dir','',[link,kind] ); } else { sendMessage('top','show_iframe_file','',[link,kind] ); } break;
  4250. default: window.open(link,'_blank'); break; // else open external document links in new tab
  4251. }
  4252. }
  4253. // click link in html files (i.e., not iframe dir index)
  4254. $('#iframe_body').on('click','a', function(e) {
  4255. if ( $(this).closest('table').attr('id') !== 'dir_list' ) { iframeClickLink(e,$(this).attr('href'),$(this).closest('span,tbody').attr('id')); }
  4256. });
  4257. // IFRAME Doubleclick iframe dir_list items (files and dirs)
  4258. function iframeDoubleClickRow(row) {
  4259. let kind = row.closest('tr').attr('data-kind'); // get item kind
  4260. if ( kind === 'audio' ) { row.closest('tr').addClass('audio_loaded selected').siblings('.audio').removeClass('audio_loaded selected'); }
  4261. let link = row.attr('href'); // get item link
  4262. switch(true) {
  4263. case ( /dir|app/.test(kind) ): sendMessage('top','show_iframe_dir','',[link,kind]); break; // dirs: tell top what to open; ignore ignored files when dblclicked
  4264. default: sendMessage('top','show_iframe_file','',[link,kind]); // files: tell top what to open; ignore ignored files when dblclicked
  4265. }
  4266. }
  4267. $('#iframe_body').on('dblclick','#tbody tr:not(.ignored) a', function(e) { e.preventDefault(); e.stopPropagation(); iframeDoubleClickRow($(this)); });
  4268. // Open IFRAME directory in sidebar
  4269. function openDirInSidebar() { let link = window.location.href; sendMessage('top','open_iframe_dir_in_sidebar','',link); }
  4270. $('#open_in_sidebar').on('click',function(e) { e.preventDefault(); openDirInSidebar(); });
  4271. // parent directory link for source directory view
  4272. $('#iframe_body').on('click','#parentDirText,#UI_goUp a.up', function(e) {
  4273. e.preventDefault(); e.stopPropagation();
  4274. $('#iframe_body').removeClass('has_stats');
  4275. if ( window.location.search.indexOf('view_directory_source') > -1 ) { window.location = $(this).closest('a').attr('href') + '?&view_directory_source=true'; }
  4276. });
  4277. //***** TEXT EDITING PANE *****//
  4278. function setUpTextEditorUI() {
  4279. let source_text, body_classes = [], content;
  4280. if ( !$('body').hasClass('has_text_editor_UI') ) { // add classes, styles, and scripts; only add once
  4281. $('head').append('<style id="text_editor_styles">'+ $text_editor_styles +'</style>');
  4282. $('head').append('<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>');
  4283. $('#title span').empty();
  4284. body_classes.push('has_text_editor_UI', getSearchParam('default_text_view'));
  4285. }
  4286. switch(true) { // get source text and append UI elements
  4287. case window.self === window.top: // top level text editor
  4288. if ( !$('body').hasClass('has_text_editor_UI') ) { $('#content_text').append( text_editing_UI_els ); } // only add the text editor UI if it hasn't been added already
  4289. break;
  4290. case window.self !== window.top: // iframe text editing UI
  4291. $('head').prepend('<meta charset="utf-8" /><meta http-equiv="Content-Type" content="text/plain; charset="utf-8">')
  4292. .append('<style id="warning_styles">'+ $warning_styles +'</style>'); // add iframe text editing styles
  4293. source_text = decodeURIComponentSafe($('body').find('> pre').text()); // get source text and decode Unicode chars.
  4294. $('body').empty().append('<div id="content_text" class="background_color_DD_33">'+ text_editing_UI_els +' </div><div id="warnings_container" class="">'+ warnings +'</div>'); // add the UI
  4295. $('#text_source').val(source_text); // set the source text value
  4296. document.getElementById('text_source').setSelectionRange(0,0); // set the insertion point to the beginning of the text
  4297. // no break
  4298. case $('#text_source').val().trim().startsWith('#EXTM3U'): // playlists and filelists
  4299. content = $('#text_source').val().trim(); // get m3u.txt file for processing
  4300. sendMessage('top','iframe_playlist','',content);
  4301. break;
  4302. }
  4303. switch(true) { // assemble text editing body classes
  4304. case getSearchParam('enable_text_editing') === 'false' && window.top !== window.self: // text editing disabled
  4305. $('body').removeClass('split_view preview_text preview_html');
  4306. body_classes.push('disable_text_editing source_text'); // show the source text
  4307. $('#text_source').prop('disabled','disabled'); // diable textarea editing
  4308. break;
  4309. default:
  4310. if ( getSearchParam('split_view') === 'true' ) { body_classes.push('split_view'); }
  4311. if ( getSearchParam('sync_scroll') === 'true' ) { body_classes.push('sync_scroll'); $('#sync_scroll input').prop({'checked':true}); }
  4312. if ( getSearchParam('editor_theme') === 'default' )
  4313. { body_classes.push('editor_theme_'+ getSearchParam('theme')); } else { body_classes.push( 'editor_theme_'+ getSearchParam('editor_theme') ); }
  4314. }
  4315. $('body').addClass( body_classes.join(' ') ); // add text editor body classes
  4316. focusTextEditorPanes();
  4317. TextEditing(); // call text editing functions
  4318. }
  4319. // setup and show top level text editor
  4320. $('#text_editor, #text_editor_row').on('click', function(e) { e.preventDefault(); showContent( $(this).attr('id') ); });
  4321. // Main Text Editing Function
  4322. function TextEditing() {
  4323. let $toolbar = $('#toolbar'), $source = $('#text_source'), $preview = $('#text_preview'), $html = $('#html_preview'), $MDhandle = $('#text_editing_handle');
  4324. // Toolbar button functions
  4325. $toolbar.on('mousedown', function(e) { e.preventDefault(); }); // prevent textarea from losing focus when clicking sidebar
  4326. // Resize
  4327. $MDhandle.on('mousedown', function(e) { e.stopPropagation(); MDresizeSplit(); }); // resize text editor panes
  4328. $MDhandle.on('dblclick', function(e) { e.stopPropagation(); $source.add($preview).add($MDhandle).add($html).removeAttr('style'); }); // reset text editor panes width
  4329. $(window).on('resize', function() { $source.add($preview).add($MDhandle).add($html).removeAttr('style'); }); // reset split to 50/50 on window resize;
  4330. // Click labels to toggle checkboxes
  4331. $preview.add($toolbar).on('click','label', function(e) { e.stopPropagation(); $(this).siblings('input').click(); });
  4332. // Sync scroll
  4333. // convert this so that function is triggered only by hovered element, not when targeted el is scrolled
  4334. $source.on('scroll', function(e) { MDsyncScroll(e,'text_source'); });
  4335. $html.on('scroll', function(e) { MDsyncScroll(e,'html_preview'); });
  4336. // $preview.on('scroll'): see "scroll_iframe" in "scroll_script" added to iframe src_doc
  4337. // Generate Markdown Preview
  4338. let source_text = ( $source.length === 0 ? '' : $source.val() );
  4339. MDmarkdown( source_text, $preview );
  4340. // Live preview update, and set edited classes for unsaved warning
  4341. $source.on('input', function() { // only add class or send message once after editing
  4342. if ( !$('body').hasClass('edited') ) {
  4343. $('body').addClass('edited'); // add edited class
  4344. if ( window.top !== window.self ) { sendMessage('top','iframe_edited','',''); } // send edited message to top
  4345. }
  4346. MDlivePreview($source,$preview);
  4347. });
  4348. // Checklists
  4349. MDsetChecklistClass();
  4350. $preview.on('click','.checklist input', function(e) { e.stopPropagation(); MDliveCheckBoxes($(this),$source,$preview); }); // Live checkboxes
  4351. $preview.on('click','.table-of-contents a', function(e) { e.preventDefault(); MDtocClick($(this),$preview); }); // Preview TOC click navigation
  4352. $preview.on('click','.uplink', function(e) { e.stopPropagation(); MDheaderClick($preview); }); // Click header uplinks
  4353. }
  4354. ///// END MAIN MD FUNCTION
  4355.  
  4356. // MARKDOWN Functions
  4357. // Focus Text
  4358. function focusTextEditorPanes() {
  4359. switch(true) {
  4360. case $('body').hasClass('split_view'): case $('body').hasClass('source_text'): $('#text_source').focus(); break;
  4361. case $('body').hasClass('preview_html'): $('#html_preview').focus(); break;
  4362. case $('body').hasClass('preview_text'): $('#text_preview').focus(); break;
  4363. }
  4364. }
  4365. // toggle text editor panes (on tab)
  4366. function toggleTextEditorPanes() {
  4367. switch(true) {
  4368. case document.activeElement.id === 'text_preview' && getFocusableEls('#text_preview').length > 0: // focus focusable elements in text preview
  4369. getFocusableEls('#text_preview').first().focus(); break;
  4370. case ( /text_preview|html_preview|text_source]/.test(document.activeElement.id) && !$('body').hasClass('split_view') ): // text editor: if not split view, focus sidebar
  4371. sendMessage('top','focus_sidebar'); break;
  4372. case $('body').hasClass('split_view'):
  4373. switch(true) {
  4374. case document.activeElement.id === 'text_source': // text editor: if text source has focus with split, focus the other pane
  4375. if ( $('body').hasClass('preview_html') ) { $('#html_preview').focus(); } else { $('#text_preview').focus(); } break;
  4376. case ( /text_preview|html_preview/.test(document.activeElement.id) ): // text editor: if text preview has focus with split, focus text source
  4377. $('#text_source').focus(); break;
  4378. }
  4379. break;
  4380. }
  4381. }
  4382. // Select Textarea Content
  4383. function selectTextareaContent(id) {
  4384. let $textarea = document.getElementById(id);
  4385. $textarea.focus();
  4386. $textarea.select();
  4387. $textarea.scrollTop = 0;
  4388. }
  4389. // TOOLBAR Clear text source
  4390. function clearText() {
  4391. if ( window.self !== window.top ) { sendMessage('top','iframe_edited'); }
  4392. $('body').removeClass('edited');
  4393. $('#text_source').val('').show().focus();
  4394. $('#text_preview').removeAttr('srcdoc');
  4395. $('#html_preview').val('');
  4396. }
  4397. $('body').on('click','#clear_text',function() { showWarning('clearText'); });
  4398. // TOOLBAR Save Button
  4399. function saveBtn(id) {
  4400. let data, ext, file_name;
  4401. switch(true) {
  4402. case $('#content_pane').attr('data-content') === 'has_text_editor': file_name = 'untitled'; break;
  4403. default: file_name = decodeURIComponentSafe(window.location.pathname.split('/').reverse()[0]);
  4404. file_name = file_name.slice(0,file_name.lastIndexOf('.'));
  4405. }
  4406. switch(true) {
  4407. case id === 'save_text': data = $('#text_source').val(); ext = '.md'; break;
  4408. case id === 'save_HTML': data = MDprepHTML($('#text_preview').html()); ext = '.html';
  4409. }
  4410. saveMD( data, file_name + ext );
  4411. }
  4412. $('#content_text').on('click','#save_btn li',function() { saveBtn($(this).attr('id')); }); // save text editor content
  4413.  
  4414. // MD SAVE SOURCE or HTML
  4415. function MDprepHTML(data) {
  4416. const save_HTML_open = `<!DOCTYPE html><html><head><meta charset="utf-8" /><title></title>
  4417. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>
  4418. <style></style><script></script></head><body lang="en" class="markdown-body">`;
  4419. const save_HTML_close = '</body></html>';
  4420. data = data.replace(/<span\sclass="uplink">.<\/span>/g,'');
  4421. return save_HTML_open + data + save_HTML_close;
  4422. }
  4423. function saveMD(data,file_name) { // #top must save text, otherwise a new window is opened that contains the blob content
  4424. if ( window.top !== window.self ) { sendMessage('top','save_text','',[data,file_name]); } else { saveFile(data,'text/plain',file_name); }
  4425. $('body,#text_source,#content_text').removeClass('edited');
  4426. }
  4427. // MD Custom pre- and post-processing for text.
  4428. function MDaddHeaderIDs(match, p1, p2, p3) { return '<h'+ p1 +' id="'+ p3.toLowerCase().replace(/\s/g,'-') +'" ' + p2 +'>'+ p3; } // create header ids for TOC
  4429. function MDcustomPreProcess(src) { return src; } // we're not doing anything here just yet...
  4430. function MDcustomPostProcess(html) {
  4431. 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
  4432. .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
  4433. // .replace(/<li><p class="checklist">"/g,'<li class="checklist"><p>')
  4434. .replace(/^<h(\d)([^>]*)>([^<]+)/gm, MDaddHeaderIDs) // add header IDs;
  4435. .replace(/<\/h(\d)>/g,'<span class="uplink">&uarr;</span></h$1>');
  4436. return html;
  4437. }
  4438. //MD Render markdown from preprocessed source text
  4439. function MDmarkdown(source_text,$preview_el) {
  4440. const MDit = window.markdownit({linkify:false, typography:false, html:true}).use(window.markdownitMultimdTable, {enableMultilineRows: true})
  4441. .use(window.markdownitSub).use(window.markdownitSup).use(window.markdownitFootnote).use(window.markdownitCentertext).use(window.markdownitDeflist).use(window.markdownitTocDoneRight);
  4442. let MD_Preview = MDcustomPostProcess( MDit.render( MDcustomPreProcess( source_text ) ) );
  4443. 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>';
  4444. $preview_el.attr('srcdoc',MD_script + MD_Preview); // set previewed text
  4445. let source_HTML = MD_Preview.toString();
  4446. $('#html_preview').empty().val(source_HTML); // set raw html
  4447. }
  4448. // MD Live preview, add edited warning
  4449. function MDlivePreview($source_el,$preview_el) { MDmarkdown( $source_el.val(),$preview_el ); MDsetChecklistClass(); }
  4450. // MD Live Checkboxes prep: find each instance of [ ] or [x] and replace text in index = to clicked checkbox in Preview.
  4451. function MDreplaceAt(str, replacement, position) { str = str.substring(0, position) + replacement + str.substring(position + replacement.length); return str; }
  4452. function MDreplaceNthSubStr(str,substr,replacement,index) {
  4453. let count = 0, found = substr.exec(str);
  4454. while ( found !== null ) { if ( count === index ) { return MDreplaceAt(str, replacement, found.index ); } else { count++; found = substr.exec(str); } }
  4455. }
  4456. // MD Live Checkboxes
  4457. function MDliveCheckBoxes(checkbox,$source_el,$preview_el) {
  4458. $('.checklist').removeClass('clicked');
  4459. checkbox.closest('p,li,dt,dd').addClass('clicked');
  4460. const this_index = $preview_el.find('.checklist').index( $('.clicked') );
  4461. const src_text = $source_el.val();
  4462. const substr = new RegExp(/\[\s*.\s*\]/g);
  4463. const replacement = ( checkbox.is(':checked') ? '[x]' : '[ ]' );
  4464. $source_el.val( MDreplaceNthSubStr(src_text, substr, replacement, this_index) );
  4465. }
  4466. // MD Checkbox list class: Prevent checkbox lists from having list bullets
  4467. function MDsetChecklistClass() { $('input[type="checkbox"]').closest('ul').css({'list-style':'none','padding':'0'}); }
  4468. // MD Resize Split View
  4469. function MDresizeSplit() {
  4470. let page_width = window.innerWidth,
  4471. editor_width = document.getElementById('content_text').offsetWidth,
  4472. editor_offsetLeft = ( document.getElementsByTagName('body')[0].id === 'top' ? document.getElementById('content_pane').offsetLeft : 0);
  4473. $(document).on('mousemove',function(e) {
  4474. e.stopPropagation(); e.preventDefault();
  4475. let pageX = e.pageX;
  4476. if ( pageX > editor_offsetLeft + 150 && pageX < page_width - 150 ) { // min widths
  4477. $('#text_editing_handle').css({'left': pageX - editor_offsetLeft - 4 + 'px'});
  4478. $('#text_source').css({'width': pageX - editor_offsetLeft + 'px'});
  4479. $('#text_preview').css({'width': editor_width + editor_offsetLeft - pageX + 'px'});
  4480. $('#html_preview').css({'width': editor_width + editor_offsetLeft - pageX + 'px'});
  4481. }
  4482. });
  4483. document.onmouseup = function() { $(document).off('mousemove'); focusTextEditorPanes(); };
  4484. }
  4485. // MD UI Sync Scroll
  4486. function MDsyncScroll(e,id,iframe_scrollTop,iframe_scrollHeight) {
  4487. if ( !document.querySelector('input[name="sync_scroll"').checked || !$('body').hasClass('split_view') ) { return; } // ignore if no split or no sync scroll
  4488. // let editor_height = document.getElementById('text_container').offsetHeight;
  4489. let scrolled = e.currentTarget, scrolled_scrollTop = iframe_scrollTop || scrolled.scrollTop,
  4490. scrolled_height = iframe_scrollHeight || scrolled.scrollHeight,
  4491. scrolled_percentage = (scrolled_scrollTop/scrolled_height).toFixed(4);
  4492. // the element to be sync scrolled: remove the target element id and the hidden editor pane from the array of editor pane ids
  4493. let synced_id = ['text_source','html_preview'].filter(el => el !== scrolled.id).filter(el => document.getElementById(el).offsetHeight > 0).toString();
  4494. let synced = document.getElementById(synced_id) || document.getElementById('text_preview').contentWindow.document.documentElement; // the element to be sync scrolled
  4495. synced.scrollTo(0, (scrolled_percentage * synced.scrollHeight).toFixed(0), {behavior:'smooth'});
  4496. // if ( (scrolled.scrollTop + editor_height) === scrolled_height) { synced.scrollTo(0,synced.scrollHeight, {behavior:'smooth'}); }; // force synced element to bottom
  4497. }
  4498. // click TOC anchors
  4499. function MDtocClick(el,$preview_el) { let thisId = el.attr('href'); if ( thisId ) { $preview_el.scrollTop( $(thisId).offset().top - 48 ); } }
  4500. // click Headers to return to TOC or top
  4501. function MDheaderClick($preview_el) {
  4502. switch(true) {
  4503. case $preview_el.find('.table-of-contents').length > 0: document.getElementsByClassName('table-of-contents')[0].scrollIntoView(true); break;
  4504. default: document.getElementById('preview').scroll(0,0);
  4505. }
  4506. }
  4507.  
  4508. //***********************//
  4509. // MESSAGES
  4510. // Send a message to iframe or parent
  4511. function sendMessage(target,message,funcName,args) {
  4512. let messageObj = { 'messageContent': message, 'functionName': funcName, 'arguments': args }, content_iframe;
  4513. switch(target) {
  4514. case 'iframe': content_iframe = document.getElementById('content_iframe'); content_iframe.contentWindow.postMessage( messageObj, '*' ); break;
  4515. case 'top': window.parent.postMessage( messageObj, '*'); break;
  4516. }
  4517. }
  4518. // Receive a message from iframe or parent, do appropriate action
  4519. function receiveMessage(e) {
  4520. if ( e.origin === 'null' || e.origin === $origin ) {
  4521. let message = e.data.messageContent, funcName = e.data.functionName, args = e.data.arguments;
  4522. switch( message ) {
  4523. case 'arrowNavigation': arrowNavigation(args); break; // class_name, key
  4524. case 'hide_sidebar': $('#hide_sidebar').click(); break;
  4525. case 'toggle_menu': $('#menu_container').click(); break;
  4526. case 'close_menu': case 'top_closed_menu': $('#iframe_body').removeClass('has_menu is_blurred'); break;
  4527. case 'top_has_menu': $('#iframe_body').addClass('has_menu is_blurred'); break; // tell iframe top has menu
  4528. case 'menu_navigation': menuNavigation(args); break; // menu navigation from iframe
  4529. case 'menu_selection': case 'clickMenu': e.preventDefault(); e.stopPropagation(); clickMenu(); break; // show menu
  4530. case 'toggle_invisibles': $('#show_invisibles_container').find('input').click(); focusContent(); break;
  4531. case 'focus_sidebar': focusSidebar(); // no break
  4532. case 'escape': case 'tab': // close menus and refocus content or focus sidebar
  4533. switch(true) {
  4534. case $('#top').hasClass('focus_content') && $('#top').hasClass('has_menu'): focusContent(); break;
  4535. default: focusSidebar(); scrollThis('tbody','selected'); break;
  4536. }
  4537. case 'shift_focus_iframe':
  4538. switch(true) {
  4539. case $('#iframe_dir_list_wrapper').length === 1: // if iframe dir_list visible...
  4540. $('body').removeClass('is_blurred');
  4541. switch(true) {
  4542. case $('tr.selected, tr.is_blurred').length > 0: $('tr.is_blurred').removeClass('is_blurred').addClass('selected'); break;
  4543. default: $('#tbody').find('tr:visible').last().addClass('selected'); // select last row when tabbing into directory
  4544. }
  4545. break;
  4546. default: getFocusableEls('#iframe_body').last().focus(); break;
  4547. }
  4548. break;
  4549. case 'focus_iframe': // after tabbing into iframe
  4550. $('body').removeClass('has_menu is_blurred');
  4551. switch(true) {
  4552. case $('#iframe_dir_list_wrapper').length === 1: // if iframe dir_list visible...
  4553. switch(true) {
  4554. case $('tr.selected, tr.is_blurred').length > 0: $('tr.is_blurred').removeClass('is_blurred').addClass('selected'); break;
  4555. default: $('#tbody').find('tr:visible').first().addClass('selected'); // select first row when tabbing into directory
  4556. }
  4557. break;
  4558. case $('body').hasClass('has_text_editor_UI'): // if text editor visible...
  4559. switch(true) { case document.activeElement.id === 'iframe_body': focusTextEditorPanes(); break; }
  4560. switch(true) { // ... and restore text selection: NOT IMPLEMENTED YET -- but should only be used for text editor, not editable file?
  4561. case $('body').hasClass('split_view'):
  4562. case $('body').hasClass('source_text'): {
  4563. let selection = window.getSelection();
  4564. if ( selection.anchorOffset > 0 ) { // restore cursor position or text selection
  4565. // document.getElementById('text_source').setSelectionRange(x,y);
  4566. // document.getElementById('text_source').scrollTop = [position of x];
  4567. // return;
  4568. } else { document.getElementById('text_source').setSelectionRange(0,0); document.getElementById('text_source').scrollTop = 0; }
  4569. break;
  4570. }
  4571. }
  4572. break;
  4573. case document.activeElement.id === 'iframe_body': // focus form elements and textareas in iframe files
  4574. // getFocusableEls('#iframe_body').first().focus();
  4575. }
  4576. break;
  4577. case 'editor_theme_default':
  4578. $('#iframe_body').removeClass('editor_theme_light editor_theme_dark').addClass(message);
  4579. break;
  4580. case 'editor_theme_light': case 'editor_theme_dark':
  4581. $('#iframe_body').removeClass('editor_theme_light editor_theme_dark').addClass(message);
  4582. break;
  4583. case 'theme_light': case 'theme_dark': // toggle iframe UI theme and iframe Text Editor theme
  4584. $('#iframe_body').toggleClass('theme_dark theme_light').removeClass('editor_theme_light editor_theme_dark').addClass('editor_'+ args );
  4585. break; // change iframe dir theme
  4586. case 'blur_iframe': $('body').addClass('is_blurred'); break;
  4587. case 'iframe_click': focusContent(); break; // close menus and fade sidebar
  4588. case 'show_iframe_dir': showContent('content_iframe_dir',args); break; // args[0] === item link, args[1] === item kind
  4589. case 'show_iframe_file': showContent('content_iframe_file',args); break; // args[0] === item link, args[1] === item kind
  4590. case 'show_content': // mainly for opening webloc and url files from
  4591. if ( args[1] === 'dir' ) { showContent('content_iframe_dir',args); } else { showContent('content_iframe_file',args); }
  4592. break;
  4593. case 'open_iframe_dir_in_sidebar': window.location = args; break; // tell top to open iframe directory in sidebar; args === iframe directory url
  4594. case 'open_iframe_parent_dir': $('#iframe_body #parent').find('a').click(); break;
  4595. case 'iframe_arrow_navigation':
  4596. switch(args) {
  4597. case 'ArrowUp': $('#tbody').find('tr:visible').last().find('a').click(); break;
  4598. case 'ArrowDown': $('#tbody').find('tr:visible').first().find('a').click(); break;
  4599. }
  4600. break;
  4601. case 'reload': $('#reload_btn').click(); break; // reload content
  4602. case 'close': $('#close_btn').click(); break; // escape content_iframe and close content
  4603. // toggle iframe dir_list UI prefs from main menu:
  4604. case 'show_numbers': case 'show_invisibles': case 'alternate_background': case 'hide_ignored_items': case 'ignore_ignored_items': $('#iframe_body').toggleClass(message); break;
  4605. // AUDIO MESSAGES
  4606. case 'iframe_play_pause_media': playPauseMedia(); break; // tell top to play/pause audio from iframe click
  4607. case 'mediaSkip': mediaSkip(undefined,args); break; // tell top to mediaskip from focused iframe
  4608. case 'play_prev_next_iframe_audio': playPrevNextMediaTrack(args); break; // play next iframe track
  4609. case 'close_iframe_audio': $('.audio_loaded').removeClass('audio_loaded'); break;
  4610. case 'set_media_duration': setMediaDuration(args[0],args[1],args[2],true); break; // set media durations for subdirectories [id, item_sort_kind, duration]
  4611. // TEXT EDITING MESSAGES
  4612. case 'iframe_edited': if ( !$('body#top').hasClass('iframe_edited') ) { $('body#top').addClass('iframe_edited'); } break; // let top know iframe text has been edited
  4613. case 'save_text_selection': $('.text.selected,.code.selected,.markdown.selected').attr('data-selection_start',args[0]).attr('data-selection_end',args[1]); break; // from iframe
  4614. case 'get_text_selection': $('#content_text').attr('data-selection_start',args[0]).attr('data-selection_end',args[1]); break;
  4615. case 'scroll_iframe': MDsyncScroll(e,'text_preview',args,document.getElementById('text_preview').contentWindow.document.documentElement.scrollHeight); break;
  4616. case 'text_editor_toolbar_button': if ( window.top !== window.self ) { $('body').toggleClass(args); } else { toggleSearchParam(args); } break;
  4617. case 'clear': $('body#top').addClass('iframe_edited'); break; // add edited class after clearing text from edited iframe file
  4618. case 'save_text': saveFile(args[0],'text/plain',args[1]); break;
  4619. case 'toggle_text_editor': showTextEditor(); break;
  4620. case 'focus_text_preview': $('#text_preview').focus(); break;
  4621. case 'unloading': // warn iframe that user wants to change or close iframe
  4622. if ( !$('#iframe_body').hasClass('has_warning') ) {
  4623. $('#iframe_body').addClass('has_warning').find('#warnings_container').removeClass().addClass('unloading').attr('data-function_name',funcName).attr('data-args',args); }
  4624. break;
  4625. case 'dont_save':// from iframe "Don't Save" button
  4626. $body.removeClass('iframe_edited'); $('tr.selected:not(.audio),tr.content_loaded').removeClass('selected content_loaded'); closeContent(); focusSidebar(); break;
  4627. // OTHERS
  4628. case 'open_link_file': openLink(args); break; // open webloc and url files
  4629. case 'iframe_loaded':
  4630. switch(true) {
  4631. case args[1] !== '' && !$('tr.selected').hasClass('cue'): processCueSheet(args[1]); break; // cue sheet
  4632. default: showIframeContent(args); // if message received by top, iframe loaded successfully; otherwise, data-loaded remains 'unloaded'
  4633. }
  4634. break;
  4635. case 'dir_list_subdir_loaded': // subdirectory loaded, add the subdirectory to the dir_list, update stats
  4636. $('.dir_list_subdir_loading').toggleClass('dir_list_subdir_loading has_subdirectory').after(args); updateStats(); break;
  4637. case 'iframe_playlist': $('tr.text').removeData('playlist').removeClass('playlist').filter('.selected').data('playlist',args).addClass('playlist'); break;
  4638. case 'get_html_content': sendMessage('top','open_in_text_editor','',document.getElementsByTagName('html')[0].outerHTML ); break; // send iframe html to top
  4639. case 'open_in_text_editor': $('#content_text').data('edit_html',args); showWarning('openInTextEditor'); break; // open text editor to edit iframe html
  4640. case 'local_link': openWarning('warning_local_file','warning_btn_ok'); break;
  4641. case 'setIframePlayerStatus': // for iframe audio playback
  4642. if ( args === 'play' ) { $('body').removeClass('is_paused').addClass('is_playing'); } else { $('body').removeClass('is_playing').addClass('is_paused'); } break;
  4643. }
  4644. }
  4645. }
  4646. window.addEventListener('message',receiveMessage,false);
  4647. // END MESSAGES
  4648.  
  4649. // WARNINGS
  4650. // list of functions to remember while sending messages and then execute after warning button click
  4651. function doFunction(funcName,args) {
  4652. var funcDictionary = { 'arrowNavigation':arrowNavigation, 'clickRow':clickRow, 'doubleClickRow':doubleClickRow, 'null':null, 'clickMenu':clickMenu, 'clickThis':clickThis, 'clearText':clearText,
  4653. 'closeButton':closeButton, 'closeContent':closeContent, 'closeFontFile':closeFontFile, 'closePlaylist':closePlaylist, 'closeGlyph':closeGlyph, 'mediaSkip':mediaSkip,
  4654. 'openSidebarInContentPane':openSidebarInContentPane, 'resetContent':resetContent, 'setLocation':setLocation, 'showDirectorySource':showDirectorySource, 'openInTextEditor':openInTextEditor };
  4655. return funcName === 'null' ? null : funcDictionary[funcName](args); // return the function and call it with args
  4656. }
  4657. // Open and Close Warning alert, focus default button
  4658. function openWarning(id,buttonid) { $('body').addClass('has_warning').find('#warnings_container').removeClass().addClass(id); focusButton(buttonid); }
  4659. function closeWarning() { $('body').removeClass('has_warning').find('#warnings_container, #warning_buttons button').removeClass(); }
  4660. // Show warning after in certain conditions (edited text, open playlist, open font file, etc.; otherwise do the action.
  4661. function showWarning(funcName,args) {
  4662. switch(true) {
  4663. case ( /arrowNavigation|clickRow/.test(funcName) ): // warnings for arrow navigation and row clicks
  4664. switch(true) {
  4665. // upon receipt of message, iframe will show its warning message, based on the funcName
  4666. case $('body').hasClass('iframe_edited'): sendMessage('iframe','unloading',funcName,args.key); focusButton('warning_btn_save'); break;
  4667. // warn with open font file and focused sidebar
  4668. case !$('body').hasClass('focus_content') && /has_font_file|has_glyph/.test( $content_pane.attr('data-content') ): openWarning('warning_close_font','warning_btn_cancel'); break;
  4669. default: doFunction(funcName,args); break;
  4670. }
  4671. break;
  4672. case ( !/arrowNavigation|clickRow/.test(funcName) ): // warnings for other functions
  4673. switch(true) {
  4674. case $content_pane.attr('data-content') === 'has_font_file': openWarning('warning_close_font','warning_btn_cancel'); break; // warn with open font file and close button.
  4675. case $('body').hasClass('has_playlist') && $content_pane.attr('data-content') === undefined && !$content_pane.hasClass('has_audio'):
  4676. case $('body').hasClass('has_filelist') && $content_pane.attr('data-content') === undefined && !$content_pane.hasClass('has_audio'):
  4677. openWarning('warning_close_playlist','warning_btn_cancel'); break;
  4678. case $('body').hasClass('edited'): case funcName === 'clearText':
  4679. $('#content_pane').removeClass('has_hidden_text_editor').attr('data-content','has_text_editor'); openWarning('unloading','warning_btn_save'); break;
  4680. default: doFunction(funcName,args); break;
  4681. }
  4682. }
  4683. }
  4684. // Warning buttons: what to do when the user clicks a warning button
  4685. function warningButtons(id) {
  4686. let btn = $(document.getElementById(id)), container_el = btn.closest('body');
  4687. switch(id) {
  4688. case 'warning_btn_dont_save': // do the user initiated func without saving the edited text
  4689. switch(true) {
  4690. case window.self !== window.top:
  4691. if ( $('#warnings_container').hasClass('unloading') ) { clearText(container_el); sendMessage('top','dont_save'); } break;// remove the irame src and body.iframe_edited class ignore
  4692. case window.self === window.top:
  4693. clearText(container_el); $content_pane.removeAttr('data-content'); $('#content_iframe').removeAttr('src').removeClass('has_content'); $dir_list.find('.dir.selected a').click();
  4694. openInTextEditor();
  4695. }
  4696. closeWarning();
  4697. break;
  4698. case 'warning_btn_cancel': closeWarning(); if ( $body.hasClass('focus_content') ) { focusContent(); } break;
  4699. case 'warning_btn_clear': closeWarning(); clearText(); break; // clear text editor
  4700. case 'warning_btn_save': if ( window.top !== window.self ) { sendMessage('top','clear'); } container_el.removeClass('edited'); $('#save_text_link').click(); closeWarning(); openInTextEditor(); break;
  4701. case 'warning_btn_ok':
  4702. switch(true) {
  4703. case $('#warnings_container').hasClass('warning_close_font'): closeFontFile(); closeWarning(); break;
  4704. case $('#warnings_container').hasClass('warning_close_playlist'): closePlaylist(); closeWarning(); break;
  4705. case $('#warnings_container').hasClass('warning_make_playlist'): makePlaylist(); break;
  4706. case $('#warnings_container').hasClass('warning_local_bookmark'): // no break
  4707. case $('#warnings_container').hasClass('warning_local_file'): // no break
  4708. case $('#warnings_container').hasClass('warning_local_playlist'): // no break
  4709. case $('#warnings_container').hasClass('warning_no_playlist'): closeWarning(); break;
  4710. }
  4711. break;
  4712. }
  4713. }
  4714. // Click Edited Warning Buttons
  4715. $('#warnings_container').on('click','button', function(e) { e.preventDefault(); e.stopPropagation(); warningButtons( $(this).attr('id') ); });
  4716. // Edited Warning overlay: prevent user clicks on rest of UI
  4717. $('body.has_overlay, body.has_warning').on('click mousedown mouseup', function(e) { e.preventDefault(); e.stopPropagation(); return; });
  4718. // Tab Warning Buttons (keyboard event)
  4719. function tabWarningButtons(e) {
  4720. switch(true) {
  4721. case e.shiftKey:
  4722. switch(true) {
  4723. case !$('#warning_buttons').find(':focus,.focus').length || !$('#warning_buttons').find(':focus,.focus').prevAll('button:visible').length:
  4724. $('#warning_buttons').find('button:visible').removeClass('focus').last().focus().addClass('focus'); break;
  4725. default: $('#warning_buttons').find(':focus,.focus').removeClass('focus').prevAll('button:visible').first().addClass('focus').focus();
  4726. }
  4727. break;
  4728. default:
  4729. switch(true) {
  4730. case !$('#warning_buttons').find(':focus,.focus').length || !$('#warning_buttons').find(':focus,.focus').nextAll('button:visible').length:
  4731. $('#warning_buttons').find('button:visible').removeClass('focus').first().focus().addClass('focus'); break;
  4732. default: $('#warning_buttons').find(':focus,.focus').removeClass('focus').nextAll('button:visible').first().addClass('focus').focus();
  4733. }
  4734. }
  4735. }
  4736. // END WARNINGS
  4737.  
  4738. // PLAYLISTS
  4739. // Open playlist
  4740. $('#menu').on('click','#open_playlist_label', function(e) { e.stopPropagation(); });
  4741. $('#menu').on('change','#open_playlist', function(e) { openFile(e,'playlist'); });
  4742. // Open Playlist/Filelist
  4743. function openPlaylist(files,reader,data) { // files and reader = open .m3u file; data = contents of m3u.txt file
  4744. if ( !$body.hasClass('has_playlist') && !$body.hasClass('has_filelist') ) { // store original dir_list and body "has_" classes as data if body does not already have playlist or filelist
  4745. let body_classes = document.getElementById('top').classList, data_classes = [];
  4746. for ( let bodyClass of body_classes.values() ) { if ( bodyClass.startsWith('has') ) { data_classes.push( bodyClass ); } } // add original body classes to dataclasses
  4747. $('#tbody').data('dir_list', $('#tbody').html() ).data('data_classes',data_classes); // store the original dir_list and classes
  4748. $body.removeClass(data_classes.join(' ')); // remove the original body classes
  4749. }
  4750. $body.removeClass('has_playlist has_filelist');
  4751. closeMenus();
  4752. let file_name = ( files !== '' ? files.name : $('tr.selected.playlist').find('.tbody_row_cell_name_a_span').text() ); // get the file name for the title and current_dir_path
  4753. let playlist_items = ( reader.result || data ); // get the playlist items from the file
  4754. if ( !playlist_items.startsWith('#EXTM3U') ) { return; } // prevent reading non-playlist files
  4755. let new_index = buildNewIndex( '', convertPlaylist(playlist_items) );
  4756. closeContent(); // if normal .m3u file, close all existing content; otherwise, leave m3u.txt files open; but what about filelists?
  4757. $('#tbody').empty().append(new_index[0]); // append the prepared playlist
  4758. if ( new_index[1].split(' ').filter(function(e) { return e !== 'has_media' && e !== 'has_audio' && e !== 'has_video' && e !==''; }).length > 0 || $('#tbody').find('tr.dir').length > 0 ) {
  4759. $body.addClass('has_filelist'); // if playlist contains non-media files or dirs
  4760. } else {
  4761. $body.addClass('has_playlist has_media'); // else normal media playlist
  4762. }
  4763. // if ( !$('#sort_by_name').hasClass('selected' ) ) { $('#sort_by_name').click(); } // sort by name
  4764. updateStats();
  4765. if ( /file:/.test(new_index) && !/file:/.test($protocol) ) { // show warning about local files on non-local page
  4766. $body.addClass('has_warning');
  4767. $('tbody').addClass('local');
  4768. openWarning('warning_local_playlist','warning_btn_ok');
  4769. }
  4770. if ( $body.hasClass('autoload_media') ) { autoLoadFile(); } // autoload media
  4771. scrollThis('tbody','selected',false);
  4772. document.title = 'Playlist: '+ file_name;
  4773. $('#current_dir_path').find('span').empty().html( file_name );
  4774. $('#open_playlist').val(''); // clear form to allow new list to be loaded
  4775. }
  4776. // Make and save playlist
  4777. function makePlaylist() {
  4778. let items = $('#tbody'), rows, playlist = [], playlistEntry = '';
  4779. let playlist_type = $('#make_playlist_form').find('input:checked').attr('id');
  4780. switch(playlist_type) {
  4781. case 'media_files_only': rows = items.children('tr.media:not(.unchecked)'); break;
  4782. case 'audio_files_only': rows = items.children('tr.audio:not(.unchecked)'); break;
  4783. case 'video_files_only': rows = items.children('tr.video:not(.unchecked)'); break;
  4784. case 'all_non_media_files': rows = items.children('tr:not(.media)'); break;
  4785. case 'all_items': rows = items.children('tr'); break;
  4786. case 'directories_only': rows = items.children('tr.dir'); break;
  4787. case 'files_only': rows = items.children('tr.file'); break;
  4788. }
  4789. switch(true) { // show warning if no qualifying items found or make playlist
  4790. case rows.length === 0:
  4791. openWarning('warning_no_playlist','warning_btn_ok');
  4792. break;
  4793. default:
  4794. for ( let i = 0; i < rows.length; i++ ) {
  4795. let row = $(rows[i]);
  4796. let link = getLinkInfo( row.find('a').attr('href') )[0].trim();
  4797. let timing = '';
  4798. playlistEntry = '#EXTINF:'+ timing +','+ row.find('a').text() +'\n'+ link;
  4799. playlist.push(playlistEntry);
  4800. }
  4801. playlist = '#EXTM3U\n'+ playlist.join('\n'); // add playlist header id
  4802. saveFile(playlist,'audio/mpeg-url','untitled.m3u'); // show save file dialogue
  4803. closeWarning();
  4804. }
  4805. }
  4806. // Select "Make Playlist" menu item: show make playlist warning list
  4807. $('#make_playlist').on('click',function(e) { e.preventDefault(); e.stopPropagation(); closeMenus(); openWarning('warning_make_playlist','warning_btn_ok'); });
  4808. // Get playlist entry
  4809. function makePlaylistEntry(id) {
  4810. let title, link, duration = '';
  4811. switch(true) {
  4812. case id === 'title': // make link for non-audio items
  4813. title = $('#title span').text();
  4814. switch(true) {
  4815. case $content_pane.attr('data-content') === 'has_image': // link for images
  4816. link = $('#content_image').attr('src');
  4817. break;
  4818. default: // all other content
  4819. link = $('.content.has_content').attr('src');
  4820. if ($('.content.has_content').attr('id') === 'content_video') { duration = Number.parseInt(getElById('video')[0].duration); }
  4821. break;
  4822. }
  4823. link = getLinkInfo(link)[0].trim();
  4824. $('#content_playlist').find('textarea').val('#EXTINF:'+ duration +','+ title +'\n'+ link +'\n').focus();
  4825. break;
  4826. case id === 'content_audio_title':
  4827. title = $('#content_audio_title span').text();
  4828. link = getLinkInfo($('#audio').attr('src'))[0].trim();
  4829. duration = Number.parseInt(getElById('audio')[0].duration); // get the track duration in secs from the media element
  4830. $('#content_audio_playlist').find('textarea').val('#EXTINF:'+ duration +','+ title +'\n'+ link +'\n').focus(); // add the entry to the textarea
  4831. break;
  4832. }
  4833. }
  4834. // Show Playlist Entry
  4835. function showPlaylistEntry(id) {
  4836. makePlaylistEntry(id);
  4837. switch(true) {
  4838. case id === 'title':
  4839. document.getElementById('content_playlist').classList.toggle('has_content');
  4840. selectTextareaContent('content_playlist_textarea');
  4841. break;
  4842. case id === 'content_audio_title':
  4843. document.getElementById('content_audio_playlist').classList.toggle('has_content');
  4844. selectTextareaContent('content_audio_playlist_textarea');
  4845. break;
  4846. }
  4847. }
  4848. $('#title, #content_audio_title').on('click',function() {
  4849. if ( $content_pane.attr('data-content') !== 'has_font_file' && $content_pane.attr('data-content') !== 'has_grid' ) { showPlaylistEntry($(this).attr('id')); }
  4850. });
  4851. // Open File
  4852. function openFile(e,type) { // type: font or playlist.
  4853. if (window.File && window.FileReader && window.FileList && window.Blob) {
  4854. let files = e.target.files[0];
  4855. let reader = new FileReader();
  4856. if ( type === 'font' ) { reader.readAsArrayBuffer(files); }
  4857. if ( type === 'playlist' ) { reader.readAsText(files); }
  4858. reader.onload = function() {
  4859. if ( type === 'font' ) { openFontFile(files,reader); }
  4860. if ( type === 'playlist' ) { openPlaylist(files,reader); }
  4861. return true;
  4862. };
  4863. } else {
  4864. alert('Can\'t open file: file APIs are not fully supported in this browser.');
  4865. }
  4866. }
  4867. // Save File
  4868. function saveFile(content,mimetype,file_name) {
  4869. let blob = new Blob([content], {type: mimetype});
  4870. let $download_el = window.document.createElement('a');
  4871. $download_el.style = "display:none";
  4872. $download_el.href = window.URL.createObjectURL(blob);
  4873. $download_el.download = file_name;
  4874. document.body.appendChild($download_el);
  4875. $download_el.click();
  4876. document.body.removeChild($download_el);
  4877. URL.revokeObjectURL(blob);
  4878. }
  4879. })();
  4880. // FINIS! † DEO GRATIAS † //