Supercharged Local Directory File Browser

Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds sidebar and preview pane; keyboard navigation and sorting; media playback with shuffle, loop, and playlist (m3u) support; preview, edit, and save markdown/plain text files; preview images and fonts; grid views for images and fonts; user-defined bookmarks; more.

As of 2020-03-28. See the latest version.

  1. /* eslint-disable no-mixed-spaces-and-tabs */
  2. /* eslint-disable no-useless-escape */
  3. /* eslint-disable no-fallthrough */
  4. /* eslint-disable indent */
  5. // ==UserScript==
  6. // @name Supercharged Local Directory File Browser
  7. // @version 5.0.1
  8. // @description Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds sidebar and preview pane; keyboard navigation and sorting; media playback with shuffle, loop, and playlist (m3u) support; preview, edit, and save markdown/plain text files; preview images and fonts; grid views for images and fonts; user-defined bookmarks; more.
  9. // @author gaspar_schot
  10. // @license GPL-3.0-or-later
  11. // @homepageURL https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser
  12. // @contributionURLhttps://paypal.me/mschrauzer
  13. // @include file://*
  14. // @include about:blank
  15.  
  16. // @require https://code.jquery.com/jquery-latest.min.js
  17.  
  18. // @require https://cdnjs.cloudflare.com/ajax/libs/markdown-it/10.0.0/markdown-it.js
  19. // @require https://cdnjs.cloudflare.com/ajax/libs/markdown-it-footnote/3.0.2/markdown-it-footnote.min.js
  20. // @require https://cdn.jsdelivr.net/npm/markdown-it-toc-done-right@2.1.0/dist/markdown-it-toc-made-right.min.js
  21. // @require https://cdn.jsdelivr.net/npm/markdown-it-sub@1.0.0/dist/markdown-it-sub.min.js
  22. // @require https://cdn.jsdelivr.net/npm/markdown-it-sup@1.0.0/dist/markdown-it-sup.min.js
  23. // @require https://cdn.jsdelivr.net/npm/markdown-it-deflist@2.0.3/dist/markdown-it-deflist.min.js
  24. // @require https://cdn.jsdelivr.net/npm/markdown-it-multimd-table@3.2.3/dist/markdown-it-multimd-table.min.js
  25. // @require https://cdn.jsdelivr.net/npm/markdown-it-center-text@1.0.4/dist/markdown-it-center-text.min.js
  26.  
  27. // @require https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js
  28.  
  29. // UPDATE URL
  30.  
  31. // NOTE: This script was developed in the lastest version of Vivaldi, running on Mac OS High Sierra. It has been tested in various Chrome- and Gecko-based browsers.
  32. // It has been not been tested in any other browsers or OSes. It should work, but please report any issues.
  33. // 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).
  34. // NOTE: By default, Greasemonkey and Tampermonkey will not run scripts on file:/// urls, so for this script to work, you will have to enable that option first.
  35.  
  36. // @namespace https://greatest.deepsurf.us/users/16170
  37. // ==/UserScript==
  38.  
  39. (function() {
  40. 'use strict';
  41. const $ = window.jQuery;
  42.  
  43. // ***** USER SETTINGS ***** //
  44. const $settings = {
  45. // NOTE: These settings will be overwritten whenever the script is updated. Use the "Export User Settings" menu item to save them.
  46. // You can paste the exported settings between the two lines below:
  47. //--------------------------------------------------------//
  48.  
  49. bookmarks: // N.B.: Directory links must end with "/", file links must end with another character.
  50. // You may add as many menus and links as you like; just copy the example below and edit as needed.
  51. // Local directory bookmarks must begin with "file:///"; external bookmarks must begin with the correct protocol ("http://" or "ftp://", etc.).
  52. [
  53. // { 'menu_title':'My Sample Menu',
  54. // 'links': [
  55. // { 'link_name':'My Directory Link 1', 'link':'file:///Path/To/My/Directory/' },
  56. // { 'link_name':'My Directory Link 2', 'link':'file:///Path/To/My/Directory_2/' },
  57. // { 'link_name':'My External Link', 'link':'https://www.mywebpage.com/' },
  58. // { 'link_name':'My File Link', 'link':'file:///Path/To/My/File.ext' },
  59. // ]},
  60. ],
  61. // GENERAL USER SETTINGS
  62. alternate_background: true, // If true (default true), alternate sidebar row background color.
  63. apps_as_dirs: false, // Un*x/Mac OS only: if true, treat apps as directories; allows app contents to be browsed. This is the default behavior for Chrome.
  64. // If false (default), treat apps as ignored files.
  65. autoload_media: true, // If true (default: true), the first audio or video file found in a directory will be automatically selected and loaded for playback; also, cover art (if any, will be loaded in the preview pane).
  66. autoload_index_files: false, // If true (default: false), automatically select first "index.xxx" (.xxx !== .htm) file found in directory.
  67. // 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.
  68. theme: 'light', // Options: 'light' or 'dark'
  69. editor_theme: 'light', // Options: if not set (default), uses "theme" setting; otherwise 'light' or 'dark'.
  70. sort_by: 'default', // Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
  71. // default = Chrome sorting: dirs on top, files alphabetical.
  72. 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").
  73. // 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.)
  74. grid_font_size: 1, // Default = 1
  75. grid_image_size: 184, // Default = 184 (200px - 16px)
  76. show_details: true, // If true (default), hide file and directory details; if false, show them.
  77. show_ignored_files: false, // If true, ignored files will appear greyed-out.
  78. // If false (default), ignored files will be completely hidden from the file list;
  79. ignore_ignored_files: true, // If true (default), ignored file types (see $row_settings below) will be hidden.
  80. // If false, ignored files will be shown, but will not be downloaded when navigated.
  81. show_invisibles: true, // Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
  82. show_numbers: true, // If true (default true), number index items
  83. UI_font: 'system-ui, sans-serif', // Choose an installed font for the UI; if undefined, use browser defaults instead.
  84. UI_font_size: '13px', // Choose a default UI font size; use any standard CSS units.
  85. use_custom_icons: true, // if true (default), use custom icons for dirs and files
  86. // if false, use browser/server default icons
  87. // TEXT EDITING SETTINGS
  88. disable_text_editing: false, // If false (default), allow plain text files to be edited.
  89. default_text_view: 'preview_text', // Options: 'source_text' or 'preview_text' for text editor.
  90. // Note that split_view = true overrides this setting.
  91. split_view: true, // If true, show split view on plain text file load.
  92. // if true (default), use default preview_text setting.
  93. sync_scroll: true // If true (default: true), show split view on plain text file load
  94. // if false, use default preview_text setting.
  95. //--------------------------------------------------------//
  96. // Paste your exported settings between the above two lines.
  97. };
  98.  
  99. // $ROW_TYPES:
  100. // DO NOT DELETE ANY EXISTING CATEGORIES BELOW!
  101. // Add file extensions for sorting and custom icon display to the existing categories.
  102. // You can also define your own new categories, but do not add an extension to more than one row_type category.
  103. // Do not add leading "." to the extensions.
  104. const $row_types = {
  105. // myRowType: ['ext1','ext2'],
  106. dir: ['/'],
  107. app: ['app/','app','bat','cgi','com','exe','jar','msi','wsf'],
  108. alias: ['alias','desktop','directory','lnk','symlink'],
  109. archive: ['7z','archive','b6z','bin','bzip','bz2','cbr','dmg','gz','iso','mpkg','pkg','rar','sit','sitx','tar','tar.gz','zip','zipx'],
  110. audio: ['aac','aif','aiff','ape','flac','m4a','mp3','ogg','opus','wav'],
  111. bin: ['a','dll','dylib','icc','msi','o'],
  112. code: ['bak','bash','bash_profile','bashrc','c','cfg','cnf','codes','coffee','conf','csh','cshrc','cson','css','custom_aliases','default','dist','editorconfig','emacs','example','gemspec','gitconfig','gitignore','gitignore_global','h','hd','ini','js','json','jsx','less','list','local','login','logout','lua','mkshrc','old','php','pl','plist','pre-oh-my-zsh','profile','pth','py','rb','rc','rdoc','sass','settings','sh','strings','taskrc','tcl','viminfo','vimrc','vue','xml','yaml','yml','zlogin','zlogout','zpreztorc','zprofile','zsh','zshenv','zshrc'],
  113. database: ['accdb','db','dbf','mdb','pdb','sql', 'sqlite','sqlitedb','sqlite3'],
  114. ebook: ['azw','azw1','azw3','azw4','epub','ibook','kfx','mobi','tpz'],
  115. font: ['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'],
  116. graphics: ['afdesign','ai','book','dtp','eps','fm','icml','idml','indd','indt','inx','mif','pmd','pub','qxb','qxd','qxp','sla','swf'],
  117. htm: ['htm','html','xhtm','xhtml'],
  118. image: ['apng','bmp','gif','ico','jpeg','jpg','png','svg','webp'],
  119. ignored_image: ['ai','arw','cr2','dng','eps','jpf','nef','psd','psd','raw','tif','tiff'],
  120. markdown: ['md','markdown','mdown','mkdn','mkd','mdwn','mdtxt','mdtext'],
  121. office: ['csv','doc','docx','epub','key','numbers','odf','ods','odt','pages','rtf','scriv','wpd','wps','xlr','xls','xlsx','xlm'],
  122. pdf: ['pdf'],
  123. system: ['DS_Store','ds_store','icon','ics'],
  124. text: ['log','nfo','txt'],
  125. video: ['m4v','mov','mp4','mpeg','webm']
  126. };
  127.  
  128. // $ROW_SETTINGS: Ignore or Exclude files by extension
  129. const $row_settings = {
  130. // Ignore: $row_types or files with extensions added here will not be loaded if selected in the sidebar (prevents the browser from attempting to download the file).
  131. ignore: $row_types.archive.concat( 'alias', $row_types.bin, $row_types.database, $row_types.graphics, $row_types.ignored_image, $row_types.office, $row_types.system),
  132. // Exclude: Files with these exensions will not be inverted in dark mode
  133. exclude: ['htm','html','xhtm','xhtml']
  134. };
  135.  
  136. // ***** END USER SETTINGS ***** //
  137.  
  138. // ## FEATURES INCLUDE:
  139. // - Resizable sidebar and directory/file preview pane.
  140. // - Arrow navigation in sidebar:
  141. // - Up and Down Arrows select next/prev item.
  142. // - Left and Right Arrows select next/prev item of same type.
  143. // - Navigate sidebar by typed string.
  144. // - Show/Hide file details (size (if avail), date modified (if avail), kind, extension).
  145. // - Sort sidebar items by name or file details.
  146. // - Default sort = sort by name with folders on top.
  147. // - Preview all file types supported natively by browser (html, text, images, pdf, audio, video, etc.) and preview fonts.
  148. // - Preview and edit markdown (if your browser supports it) and plain text files, with option to save files locally.
  149. // - Markdown rendered with markdownit.js ( https://github.com/markdown-it/markdown-it ).
  150. // - Uses Github Markdown styles for preview ( https://github.com/sindresorhus/github-markdown-css ), with a few customizations.
  151. // - Support for:
  152. // - TOC creation ( `${toc}` ) ( https://github.com/nagaozen/markdown-it-toc-done-right )
  153. // - Multimarkdown table syntax ( https://github.com/RedBug312/markdown-it-multimd-table )
  154. // - Live checkboxes ( `\[ ], [x]` ), allowed in lists and deflists.
  155. // - Superscript ( `^sup^` ) ( https://github.com/markdown-it/markdown-it-sup )
  156. // - Subscript ( `~sub~` ) ( https://github.com/markdown-it/markdown-it-sub )
  157. // - Definition lists ( https://github.com/markdown-it/markdown-it-deflist; for syntax, see http://pandoc.org/MANUAL.html#definition-lists )
  158. // - Centered text ( `->centered<-` ) ( https://github.com/jay-hodgson/markdown-it-center-text )
  159. // - Footnotes ( https://github.com/markdown-it/markdown-it-footnote )
  160. // - View source text, preview, or split pane with proportional sync scroll.
  161. // - Save edited source text or previewed HTML.
  162. // - Create and edit text in separate text editor.
  163. // - Audio and video playback, with shuffle, loop, skip audio +/- 10 or 30 sec via keyboard.
  164. // - Preview other files (e.g., lyrics or cover art) in same directory while playing audio.
  165. // - User setting to autoload cover art (if any images in directory, load "cover.ext" or first image found)
  166. // - Grid views for images and fonts.
  167. // - Playlists and filelists.
  168. // - Open standard .m3u media files; save .m3u file of all media items in current directory.
  169. // - Open and save custom .m3u files that may contain directories and _any_ file type.
  170. // - Click file or audio title to display .m3u listing for that item.
  171. // - User settings (see $settings in code; some settings can be changed via the main menu in the UI and will be remembered in URL query):
  172. // - Light or Dark theme.
  173. // - Bookmarks for local or remote directories.
  174. // - Default image grid size.
  175. // - Default UI font size and font-family.
  176. // - Default UI font and font-size.
  177. // - Default file sorting.
  178. // - Sort with directories on top.
  179. // - Treat apps as directories (MacOS and *nix only)
  180. // - Show or hide invisible files.
  181. // - Show or hide ignored files in the ignored files list (see $row_settings in code below $settings).
  182. // - Show or hide file details.
  183. // - Use custom file icons or browser defaults.
  184. // - Autoload index.ext files.
  185. // - Autoload cover art in directories with audio files.
  186. // - Text editing default view: split, source, or preview.
  187. // - Text editing sync scroll: on or off.
  188.  
  189. // ************ J + M + J ************* //
  190.  
  191. // ************************************ //
  192. // DON'T EDIT ANYTHING BELOW THIS LINE. //
  193. // ************************************ //
  194.  
  195. // ***** GENERAL SETUP ***** //
  196. function getBrowser() {
  197. switch(true) {
  198. case navigator.userAgent.search('Chrome') >= 0: return 'chrome';
  199. case navigator.userAgent.search('Firefox') >= 0: return 'firefox';
  200. case navigator.userAgent.search('MSIE') >= 0: return 'explorer';
  201. case navigator.userAgent.search('Opera') >= 0: return 'opera';
  202. case navigator.userAgent.search('Safari') >= 0 && navigator.userAgent.search('Chrome') < 0: return 'safari';
  203. }
  204. }
  205. function getOS() { // modded from https://stackoverflow.com/questions/38241480/detect-macos-ios-windows-android-and-linux-os-with-js
  206. var platform = window.navigator.platform, macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'], os = null;
  207. switch(true) {
  208. case macosPlatforms.indexOf(platform) !== -1: os = 'macos'; break;
  209. case windowsPlatforms.indexOf(platform) !== -1: os = 'windows'; break;
  210. // case iosPlatforms.indexOf(platform) !== -1: os = 'ios'; break; // just in case;
  211. // case /Android/.test(userAgent): os = 'android'; break; // just in case;
  212. case !os && /Linux/.test(platform): os = 'linux'; break;
  213. }
  214. return os;
  215. }
  216. // OTHER
  217. // var context = new AudioContext(); // needed to allow continuous playback of audio
  218. // PATHS
  219. function decodeURIComponentSafe(s) { // Fix "%" error in file name; see https://stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser
  220. if ( !s ) { return s; }
  221. try {
  222. return decodeURIComponent(s.replace(/%(?![0-9a-fA-F]{2})/g, '%25') ); // replace % with %25 if not followed by two a-f/number
  223. } catch(e) {
  224. return s;
  225. }
  226. }
  227. const $protocol = window.location.protocol;
  228. const $origin = $protocol +'//'+ window.location.host;
  229. let $location = decodeURIComponentSafe( [location.protocol, '//', location.host, location.pathname].join('') );
  230. const $current_dir_path = $location.replace(/([/|_|—])/g,'$1<wbr>').replace(/\\/g,'/'); // URL w/o query string for display
  231. const $current_dir = $location.split('/').slice(-2,-1).toString();
  232.  
  233. function escapeStr(str) { str = str.replace(/([$?*+()[]|^])/g,'\$1'); return str; }
  234.  
  235. // if URL is a file, change window location to parent dir, add querystring of file name; then autoload file.
  236. function loadFile() {
  237. let $query_prefs = getQueryPrefs();
  238. $query_prefs.set( 'file', $location.slice($location.lastIndexOf('/') + 1) );
  239. window.location = $location.slice(0,$location.lastIndexOf('/') + 1) +'?'+ $query_prefs;
  240. return;
  241. }
  242. if ( $location.slice($location.lastIndexOf('/')).indexOf('.') !== -1 && !$location.endsWith('/') && window.top === window.self ) {
  243. loadFile();
  244. }
  245. // QUERY PREFS
  246. function getQueryPrefs() { return new URL(window.location).searchParams; }
  247. // const initialQueryPrefs = getQueryPrefs();
  248. // set query key/value
  249. function setQuery(key, value) {
  250. let $query_prefs = getQueryPrefs();
  251. $query_prefs.set( key, value );
  252. updateQuery($query_prefs);
  253. }
  254. // get query value
  255. function getQuery(key) {
  256. let $query_prefs = getQueryPrefs();
  257. let value = '';
  258. if ( key === 'width' ) {
  259. value = ( !$query_prefs.has(key) || window.innerWidth === 0 ? 30 : Math.round(100 * Number.parseInt($query_prefs.get('width'))/window.innerWidth) ); // percentage
  260. } else {
  261. value = ( $query_prefs.has(key) ? $query_prefs.get(key) : $settings[key] !== undefined ? $settings[key].toString() : '' );
  262. value = value.replace('%2F','').replace('/',''); // some servers add a '/' to end of query string
  263. }
  264. return value;
  265. }
  266. // toggle query key
  267. function toggleQuery(key) {
  268. let $query_prefs = getQueryPrefs();
  269. let nonBoolPrefs = {
  270. 'theme_light': {'theme':'dark'},
  271. 'theme_dark': {'theme':'light'},
  272. 'source_text': {'default_text_view':'preview_text'},
  273. 'preview_text': {'default_text_view':'source_text'},
  274. 'sort_by_default': {'sort_by':'default'},
  275. 'sort_by_name': {'sort_by':'name'},
  276. 'sort_by_size': {'sort_by':'size'},
  277. 'sort_by_date': {'sort_by':'date'},
  278. 'sort_by_kind': {'sort_by':'kind'},
  279. 'sort_by_ext': {'sort_by':'ext'}
  280. };
  281. var value, queryValue, settingsValue;
  282. if ( nonBoolPrefs[key] !== undefined ) {
  283. value = Object.values(nonBoolPrefs[key]).toString();
  284. key = Object.keys(nonBoolPrefs[key]).toString(); // must come after value: i.e., don't redefine key before getting value
  285. if ( $settings[key] === value ) { $query_prefs.delete( key ); } else { $query_prefs.set( key, value ); }
  286. } else {
  287. queryValue = $query_prefs.get(key);
  288. settingsValue = $settings[key];
  289. value = ( queryValue === null ? settingsValue.toString() : queryValue.toString() );
  290. value = ( value === 'true' ? 'false' : 'true' );
  291. if ( ( queryValue !== null && queryValue !== settingsValue ) ) {
  292. $query_prefs.delete( key );
  293. } else {
  294. $query_prefs.set( key, value );
  295. }
  296. }
  297. updateQuery($query_prefs);
  298. }
  299. // remove query key
  300. function removeQuery(key) {
  301. let $query_prefs = getQueryPrefs();
  302. $query_prefs.delete(key);
  303. updateQuery($query_prefs);
  304. }
  305. // update query string
  306. function updateQuery(querystr) {
  307. querystr = querystr.toString().replace('%2F','').replace('/','');
  308. window.history.replaceState({}, document.title, window.location.pathname +'?'+ querystr);
  309. updateParentLinks();
  310. }
  311.  
  312. // Test some array 1 in array 2 or all array 1 in array 2
  313. function arr1InArr2(arr1,arr2,bool) { // bool 'true' = some in array; else all in array
  314. if ( bool !== true ) { // 'some'
  315. return arr1.some(r => Array.from(arr2).includes(r));
  316. } else { // 'all'
  317. return arr1.every(r => Array.from(arr2).includes(r));
  318. }
  319. }
  320. // ***** SET UP UI ELEMENTS ***** //
  321. // Parent and Parents Menus
  322. // UTILITIES
  323. function updateQueryStr(str) {
  324. str = str.replace(/([^\?]*)selected=[^&]*(.*)$/m,'$1$2') // delete current selected query, if any
  325. .replace(/([^\?]*)history=(\d+)\+*([^&]*)(&*)(.*)/m,'$1$4$5&selected=$2&history=$3').replace(/&{2,}/g,'&').replace(/\?&/m,'\?'); // format query with selected and history at end
  326. if ( str.endsWith('&history=') ) { str = str.replace(/(.+)&history=$/m,'$1'); } // if no history, delete query
  327. return str;
  328. }
  329. // create links
  330. function updateParentLinks() { $('#parents_dir_nav').siblings('ul').empty().append( createParentLinkItems() ); }
  331. function createParentLinks() {
  332. let links = [];
  333. let str = decodeURIComponentSafe(window.location.search);
  334. str = str.replace('/','').replace('%2F','');
  335. let linkPieces = $location.split('/');
  336. linkPieces = linkPieces.slice(2,-2); // remove beginning and ending empty elements and current directory
  337. while ( linkPieces.length > 0 ) { // while there are link pieces...
  338. str = updateQueryStr(str); // update selected and history
  339. let link = $protocol +'//'+ linkPieces.join('/') +'/'+ str; // assemble link
  340. links.push(link); // add to link array
  341. linkPieces.pop(); // remove last link piece and repeat...
  342. }
  343. if ( links.length === 0 ) { links.push($protocol +'///'+ str); } // fix for top level local pages
  344. return links;
  345. }
  346. // create menu items
  347. function createParentLinkItems() {
  348. let $parent_link_menu_items = [];
  349. let $links = createParentLinks();
  350. $('#parent_dir_nav').find('a').attr( 'href', $links[0] ); // set parent link
  351. for ( let i = 0; i < $links.length; i++ ) {
  352. let display_name = $links[i].slice(0,$links[i].lastIndexOf('?') - 1);
  353. display_name = display_name.replace(/\//g,'\/<wbr>');
  354. let menu_item = '<li><a href="'+ $links[i] +'" class="text_color_111">' + display_name + '/</a></li>';
  355. $parent_link_menu_items.push(menu_item);
  356. }
  357. return $parent_link_menu_items; // return parents link items
  358. }
  359. // MENUS: User bookmarks
  360. function bookmarksMenuItems() {
  361. const $bookmarks = $settings.bookmarks;
  362. let menu_items = [];
  363. let $links_arr = [];
  364. let $links_arr_str = '';
  365. let $links;
  366. if ( $bookmarks.length > 0 ) {
  367. for ( let i = 0; i < $bookmarks.length; i+=1 ) {
  368. $links = $bookmarks[i].links;
  369. // make array of links
  370. for ( let j = 0; j < $links.length; j+=1 ) {
  371. if ( !$links[j].link.endsWith('/') ) {
  372. $links[j].link = $links[j].link.slice(0,$links[j].link.lastIndexOf('/') + 1) + '?file=' + $links[j].link.slice($links[j].link.lastIndexOf('/') + 1) ;
  373. }
  374. $links[j].link_name = $links[j].link_name.split('/').join('/<wbr>');
  375. $links_arr[j] = '<li><a class="menu_item has_icon_before text_color_111" href="'+ $links[j].link +'">' + $links[j].link_name + '</a></li>';
  376. }
  377. $links_arr_str = $links_arr.join('');
  378. menu_items[i] = '<li class="bookmark has_submenu"><a class="menu_item text_color_111">'+ $bookmarks[i].menu_title +'</a><ul class="submenu background_color_D0_50 border_all">'+ $links_arr_str +'</ul></li>';
  379. }
  380. menu_items = menu_items.join('');
  381. }
  382. return menu_items;
  383. }
  384. // MENUS: Other menu items
  385. // #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
  386. const SidebarMenuItems = function() {
  387. let sort_by = '<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"><span class="menu_item">Name</span></li><li id="size"><span class="menu_item">Size</span></li><li id="date"><span class="menu_item">Date</span></li><li id="kind"><span class="menu_item">Kind</span></li><li id="ext"><span class="menu_item">Extension</span></li><li id="default"><span class="menu_item">Default</span></li></ul>';
  388. let autoload_media = '<li id="autoload_media" class="toggle_UI_pref border_bottom" title="Automatically select and load the first media item in a directory."><span id="autoload_media_menu" class="menu_item">Autoload Media</span></li>';
  389. let theme = '<li title="Set the main UI theme (light or dark)."><span id="theme" class="menu_item toggle_UI_pref"><span> Theme</span></span></li>';
  390. let alternate_background = '<li id="alternate_background" class="toggle_UI_pref" title="Alternate backgrounds of directory items."><span class="menu_item">Alternate Backgrounds</span></li>';
  391. let show_numbers = '<li id="show_numbers" class="toggle_UI_pref" title="Number directory list items."><span class="menu_item">Show Numbers</span></li>';
  392. let ignore_ignored_files = '<li id="ignore_ignored_files" class="toggle_UI_pref border_bottom" title="Show/hide ignored files. Ignored file types from the list in the settings will not be loaded when browsing directories."><span class="menu_item">Hide Ignored Files</span></li>';
  393. let text_editing = '<li id="text_editing" class="has_submenu"><span class="menu_item">Text Editing</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="split_view" class="toggle_UI_pref border_bottom" title="Toggle display of default text view and both source and rendered text."><span id="toggle_split_view" class="menu_item">Split View</span></li><li id="preview_text_menu_item" title="Set the default text view for non-split view."><span class="toggle_UI_pref menu_item" id="source_text">Source Text</span><span class="toggle_UI_pref menu_item" id="preview_text">Preview Text</span></li></ul>';
  394. let disable_text_editing = '<li class="border_bottom" title="Enable/disable editing of plain text files. Does not effect main text editor."><span class="menu_item toggle_UI_pref" id="disable_text_editing"><span id="disable">Text Editing </span></span></li>';
  395. let open_playlist = '<li><label id="open_playlist_label" class="menu_item" 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>';
  396. let make_playlist = '<li class="border_bottom"><a 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;</a></li>';
  397. let open_font = '<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&hellip;</label><input type="file" id="open_font" name="open_font" accept=".otf,.ttf,.woff"></input></li>';
  398. let default_settings = '<li><a id="default_settings" class="menu_item text_color_111" href="#" title="Delete UI prefs stored in the URL query string and reload page.">Default User Settings</a></li>';
  399. let export_settings = '<li class="border_bottom"><a id="export_settings" class="menu_item text_color_111" href="#" title="Export hard-coded user settings and bookmarks to text file.">Export User Settings</a></li>';
  400. let help_link = '<li id="show_help"><span class="menu_item text_color_111">Help</span></li>';
  401. let contact_link = '<li id="contact"><a class="menu_item text_color_111" href="mailto:mshroud@vivaldi.net">Contact</a></li>';
  402. let donate_link = '<li id="donate"><a class="menu_item text_color_111" href="https://paypal.me/mschrauzer" target="_blank" rel="noopener">Donate</a></li>';
  403. return sort_by + theme + alternate_background + show_numbers + ignore_ignored_files + autoload_media + text_editing + disable_text_editing + open_playlist + make_playlist + open_font + default_settings + export_settings + help_link + contact_link + donate_link;
  404. };
  405. const SidebarHeaderEls = function() {
  406. let checked = '';
  407. if ( getQuery('show_invisibles') === 'true' ) { checked = 'checked'; }
  408. let parent_link_items = createParentLinkItems();
  409. let parent_link = $(parent_link_items[0]).find('a').attr('href');
  410. parent_link_items = parent_link_items.toString().replace(/<\/li>,<li>/g,'</li><li>');
  411. let parent_dir_nav = '<nav id="parent_dir_nav" class="invert"><a class="menu_item" href="'+ parent_link +'" title="Parent Directory">&nbsp;</a></nav>';
  412. let parents_dir_nav = '<nav id="parents_dir_nav" class="border_right border_left"><div id="current_dir_path" title="Parent Directories">'+ $current_dir_path +'</div></nav><ul id="parents_links" class="menu background_color_D0_50 border_bottom">'+ parent_link_items +'</ul>';
  413. let menu_nav = '<nav id="menu_nav" class="invert"><div>&nbsp;</div></nav><ul id="menu" class="menu background_color_D0_50 border_bottom">'+ bookmarksMenuItems() + SidebarMenuItems() +'</ul>';
  414. let show_details = '<button id="show_details" class="toggle_UI_pref" tabindex="-1" title="Toggle display of directory item detail information"><span id="show"> details</span></button>'; // "Show/Hide" prefixed in pseudo-element
  415. let inv_checkbox = '<label id="show_invisibles_container"><input class="toggle_UI_pref" type="checkbox" id="show_invisibles" for="inv_checkbox" name="inv_checkbox" tabindex="-1" '+ checked +' /><span class="text_color_111">Show Invisibles</span></label>';
  416. let grid_btn = '<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>';
  417. let sorting = '<div id="sorting" class="background_color_C0_40"><div id="sorting_row_1" class="container"><div class="toggle_UI_pref name sorting" id="sort_by_name" title="Sort by name" colspan="2"><span><input id="play_toggle" type="checkbox" tabindex="-1" checked="true" />Name</span></div><div class="toggle_UI_pref sorting" id="sort_by_default" title="Default sort" colspan="2"><span>Default</span></div></div><div id="sorting_row_2"><div class="toggle_UI_pref details sorting" id="sort_by_size" title="Sort by size"><span>Size</span></div><div class="toggle_UI_pref details sorting" id="sort_by_date" title="Sort by size"><span>Date</span></div><div class="toggle_UI_pref details sorting" id="sort_by_kind" title="Sort by kind"><span>Kind</span></div><div class="toggle_UI_pref details sorting" id="sort_by_ext" title="Sort by extension"><span>Ext</span></div></div></div>';
  418. let text_editor_row = '<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. let sidebar_header_head = '<div id="sidebar_title" class="border_bottom background_color_B0_30"><div></div></div>';
  420. let sidebar_menus = '<div id="sidebar_menus" class="background_color_B0_30 border_bottom"><div id="parent_dir_menu">'+ parent_dir_nav +'</div><div id="parents_dir_menu">'+ parents_dir_nav +'</div><div id="menu_container" title="Main menu">'+ menu_nav +'</div></div>';
  421. let sidebar_buttons = '<div id="sidebar_buttons" class="background_color_C0_40 border_bottom"><div id="sidebar_buttons_left" colspan="3">'+ show_details + inv_checkbox +'</div>'+ grid_btn +'</div>';
  422. let sidebar_header_body = '<div id="sidebar_header_body" class="border_bottom">'+ sidebar_menus + sidebar_buttons + sorting + text_editor_row +'</div>';
  423. let sidebar_header_els = '<section id="sidebar_header" class="text_color_111">'+ sidebar_header_head + sidebar_header_body +'</section>';
  424. return sidebar_header_els;
  425. };
  426. // Sidebar Footer (Stats)
  427. const SidebarFooterEls = function() {
  428. const stats_summary = '<div id="stats_summary" class="background_color_C0_40">&nbsp;</div>';
  429. const stats_summary_detailed_total = '<div id="stats_summary_detailed_total" class="summary_detailed border_bottom background_color_C0_40"></div>';
  430. const stats_summary_detailed_dirs = '<div id="stats_summary_detailed_dirs" class="dir summary_detailed"></div>';
  431. const stats_summary_detailed_files = '<div id="stats_summary_detailed_files" class="file summary_detailed border_bottom"></div>';
  432. const stats_summary_playlist_container = '<div id="stats_summary_playlist_container" class="background_color_C0_40"><div id="stats_summary_playlist_files" class="summary_detailed"></div></div>';
  433. const stats_summary_detailed_container = '<div id="stats_summary_detailed_container" class="background_color_E0_50">' + stats_summary_detailed_total + stats_summary_detailed_dirs + stats_summary_detailed_files +'</div>';
  434. const stats_details_container = '<div id="stats_details_container" class="background_color_E0_50"></div>';
  435. const stats = stats_summary + stats_summary_detailed_container + stats_summary_playlist_container + stats_details_container;
  436. let dir_list_foot = '<section id="tfoot" class="background_color_D0_50 border_top"><div id="stats" class="text_color_111" title="Click to toggle additional information">'+ stats +'</div><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 in Content Pane</li><li id="view_directory_source" class="text_color_111" data-kind="view_directory_source" title="View current directory index source">View directory source</li></ul></div></section>';
  437. return dir_list_foot;
  438. };
  439. // Dir List Elements
  440. const SidebarDirListEls = function() {
  441. let dir_list_body = '<tbody id="tbody" class="background_color_DD_44 text_color_111" tabindex="0"></tbody>';
  442. let sidebar_dir_list_els = '<section id="dir_list_wrapper"><table id="dir_list">'+ dir_list_body +'</table></section>';
  443. return sidebar_dir_list_els;
  444. };
  445. // CONTENT PANE ELEMENTS
  446. const ContentHeaderEls = function() {
  447. let title_buttons_left = '<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"><span id="prev_btn"><span>&nbsp;</span></span><span id="next_btn"><span>&nbsp;</span></span></button></div>';
  448. let title = '<div id="title" class=""><span></span></div>';
  449. let title_buttons_right = '<div id="title_buttons_right" class="title_right"><button id="close_btn" tabindex="-1"><span></span></button><button id="scale" class="split_btn"><span id="decrease">&nbsp;</span><span id="increase">&nbsp;</span></button></div>';
  450. let content_playlist = '<div id="content_playlist" class="playlist_entry_container border_bottom"><textarea id="content_playlist_textarea" rows="3" spellcheck="false" /></div>';
  451. let content_title = '<div id="content_title" class="title border_bottom">'+ title_buttons_left + title + title_buttons_right +'</div>'+ content_playlist;
  452. let content_header_els = '<header id="content_header"><div class="text_color_111 background_color_B0_30"><div>'+ content_title + ContentAudioEls() +'</div></div></header>';
  453. return content_header_els;
  454. };
  455. // Content containers
  456. const ContentEls = function() {
  457. let content_grid = '<div id="content_grid" data-kind="grid"></div>';
  458. let content_text = '<div id="content_text" class="background_color_DD_33"></div>';
  459. let content_font = '<div id="content_font" class="content background_color_FF_11 text_color_111" spellcheck="false" data-kind="font">'+ ContentFontEls() +'</div>';
  460. let content_image = '<div id="content_image" class="content background_color_FF_11" data-kind="image"><img tabindex="0"/></div>';
  461. let content_video = '<video id="content_video" class="content background_color_FF_11 media" controls data-kind="video">Your browser does not support the video tag.</video>';
  462. let content_pdf = '<embed id="content_pdf" class="content" name="plugin" tabindex="0" data-kind="pdf"></embed>';
  463. let content_iframe = '<iframe id="content_iframe" class="content" name="content_iframe" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" tabindex="0" data-kind="file"></iframe>';
  464. let content_els = '<div id="content_container" class="background_color_EE_22">'+ content_grid + content_text + content_font + content_image + content_pdf + content_video + content_iframe +'</div>';
  465. return content_els;
  466. };
  467. // Content Audio Els
  468. const ContentAudioEls = function() {
  469. let prev_track = '<div id="prev_track" class="prev_next_track_btn audio_controls" title="Previous track">&nbsp;</div>';
  470. let next_track = '<div id="next_track" class="prev_next_track_btn audio_controls" title="Next track">&nbsp;</div>';
  471. let audio_player = '<audio id="audio" preload="auto" tabindex="0" controls controlsList="nofullscreen" >Sorry, your browser does not support HTML5 audio.</audio>';
  472. // let audio_player = '<iframe id="audio_iframe" sandbox="allow-scripts allow-same-origin allow-modals allow-popups"></iframe>';
  473. let loop = '<label id="loop_label"><input type="checkbox" id="loop" for="loop" name="loop" tabindex="0" />Loop</label>';
  474. let shuffle = '<label id="shuffle_label"><input type="checkbox" id="shuffle" for="shuffle" name="shuffle" tabindex="0" />Shuffle</label>';
  475. let audio_options = '<div id="audio_options">'+ loop + shuffle +'</div>';
  476. let close_audio = '<div id="close_audio" class="audio_controls" title="Close audio"></div>';
  477. let content_audio_playlist ='<div id="content_audio_playlist" class="playlist_entry_container border_bottom"><textarea id="content_audio_playlist_textarea" rows="3" spellcheck="false" /></div>';
  478. let content_audio_title = '<div id="content_audio_title" title="Click to toggle .m3u playlist entry."><span></span></div>';
  479. let content_audio_els = content_audio_title +'<div id="content_audio" class="border_bottom"><div id="audio_container">'+ prev_track + next_track + audio_player + close_audio +'</div>'+ audio_options +'</div>'+ content_audio_playlist;
  480. return content_audio_els;
  481. };
  482. // Content Font Els
  483. const ContentFontEls = function() {
  484. let sample_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789 [(!@#$%^&*;:)]';
  485. let specimen = '<div id="specimen" class="specimen border_bottom_x" contenteditable="true" tabindex="0">'+ sample_string +'</div>';
  486. let specimen_2 = '<h1 id="specimen_2">Typography</h1><h4 id="specimen_2H4">The art of using types to produce impressions on paper, vellum, &amp;c.</h4>';
  487. let specimen_3 = '<h2 id="specimen_3">S P E C I M E N</h2>';
  488. let specimen_3H3 = '<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>';
  489. let 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.';
  490. let lorem = '<div id="lorem" class="lorem" contenteditable="true">'+ lorem_string +'</div>';
  491. let lorem_2 = '<div id="lorem_2" class="lorem" contenteditable="true">'+ lorem_string +'</div>';
  492. let lorem_3 = '<div id="lorem_3" class="lorem" contenteditable="true">'+ lorem_string +'</div>';
  493. let specimen_strings = '<div id="specimen_string_2" class="hamburger border_bottom_x" contenteditable="true" tabindex="0">'+ specimen_2 +'</div><div id="specimen_string_3" class="hamburger border_bottom_x" contenteditable="true">'+ specimen_3 + specimen_3H3 +'</div><div id="lorem_strings" contenteditable="true" tabindex="0">'+ lorem + lorem_2 + lorem_3 +'</div>';
  494. let font_specimen = '<div id="font_specimen">'+ specimen + specimen_strings +'</div>';
  495. let glyph_viewer_info = '<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>';
  496. let glyph_viewer = '<div id="glyph_viewer" class="background_color_11_FF invert">'+ glyph_viewer_info +'</div>';
  497. let content_font_els = font_specimen + ContentFontViewer() + glyph_viewer;
  498. return content_font_els;
  499. };
  500. // Font Viewer
  501. const ContentFontViewer = function() {
  502. let glyphs_container = '<div id="glyphs_container"></div>';
  503. let font_viewer = '<div id="font_viewer">'+ glyphs_container +'</div>';
  504. return font_viewer;
  505. };
  506. // Iframe Dir Elements
  507. const ContentIframeDirEls = function(parentLink) {
  508. let parent_link_cell = '<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>';
  509. let sorting_row = '<div id="sorting_row_1" class="border_bottom text_color_111 background_color_C0_40"><div class="sorting" id="sort_by_name" title="Sort by name"><span>Name</span></div><div class="sorting" id="sort_by_default" title="Default sort"><span>Default</span></div></div><div id="sorting_row_2" class="border_bottom background_color_C0_40 text_color_111"><div class="sorting" id="sort_by_ext" title="Sort by extension"><span>Ext</span></div><div class="sorting" id="sort_by_size" title="Sort by size"><span>Size</span></div><div class="sorting" id="sort_by_date" title="Sort by date"><span>Date</span></div><div class="sorting" id="sort_by_kind" title="Sort by kind"><span>Kind</span></div></div>';
  510. let iframe_dir_els = '<header id="thead">'+ parent_link_cell + sorting_row +'</header><section id="iframe_dir_list_wrapper"><table id="dir_list"><tbody id="tbody" class="background_color_DD_44 text_color_111"></tbody></table></section>';
  511. return iframe_dir_els;
  512. };
  513. // Warnings
  514. const Warnings = function() {
  515. let warning_close_font = '<div id="warning_close_font" class="warning">Are you sure you want to close the font preview?</div>';
  516. let warning_unsaved_text = '<div id="warning_unsaved_text" class="warning">You have unsaved changes.</div>';
  517. let warning_clear_text = '<div id="warning_clear_text" class="warning">Are you sure you want to clear all your text?</div>';
  518. let warning_local_bookmark = '<div id="warning_local_bookmark" class="warning">Can\'t load local directories or files from non-local web pages. <br />&emsp;Please use your browser\'s bookmarks instead or enter the URL manually.</div>';
  519. let warning_close_playlist = '<div id="warning_close_playlist" class="warning">Are you sure you want to close the playlist?</div>';
  520. let warning_local_playlist = '<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>';
  521. let warning_no_playlist = '<div id="warning_no_playlist" class="warning">Can’t make playlist: no qualified items found.</div>';
  522. let warning_make_playlist = '<div id="warning_make_playlist" class="warning"><form id="make_playlist_form" action="#"><fieldset><div><input name="make_playlist" type="radio" id="media_files_only" checked><label for="media_files_only">All media files</label></div><div class="indent"><input name="make_playlist" type="radio" id="audio_files_only"><label for="audio_files_only">Audio files only</label></div><div class="indent"><input name="make_playlist" type="radio" id="video_files_only"><label for="video_files_only">Video files only</label></div><div><input name="make_playlist" type="radio" id="all_non_media_files"><label for="all_non_media_files">All non-media items</label></div><div><input name="make_playlist" type="radio" id="all_items"><label for="all_items">All items</label></div><div class="indent"><input name="make_playlist" type="radio" id="directories_only"><label for="directories_only">Directories only</label></div><div class="indent"><input name="make_playlist" type="radio" id="files_only"><label for="files_only">Files only</label></div></fieldset></form></div>';
  523. let warning_buttons = '<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>';
  524. let warnings = '<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</span></h3></div><div id="warnings" class="text_color_111 background_color_D0_50">'+ warning_close_font + warning_unsaved_text + warning_clear_text + warning_local_bookmark + warning_close_playlist + warning_local_playlist + warning_no_playlist + warning_make_playlist +'</div><div id="warning_buttons_container" class="background_color_E0_60">'+ warning_buttons +'</div>';
  525. return warnings;
  526. };
  527. // Help
  528. const $content_help = `
  529. <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>
  530. <table id="content_help" class="background_color_C0_40 text_color_111 border_top_x border_right_x border_left_x">
  531. <tbody class="">
  532. <tr><td class="kbd_shortcut text_color_111 border_right_x">KEY</td><td class="help_description text_color_111">DESCRIPTION</td></tr>
  533. <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 />
  534. 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>
  535. <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 />
  536. If current selection is a media file, select and begin playback of the next media item.</td></tr>
  537. <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>
  538. <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>
  539. <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>
  540. <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>
  541. <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>
  542. <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>
  543. <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>
  544. <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>
  545. <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>
  546. <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>
  547. <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>
  548. <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>
  549. <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>
  550. <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>
  551. <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>
  552. <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>
  553. <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>
  554. <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>
  555. <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>
  556. </tbody>
  557. </table>
  558. <div>Main Menu
  559. <dl>
  560. <dt>Open Playlist/Filelist</dt>
  561. <dd>The script supports basic .m3u playlists of audio or video files, as well as custom "filelists" which can include directories and any file types supported by the browser.</dd>
  562. </dl>
  563. </div>`;
  564. // MD Build UI
  565. const TextEditingUIEls = function() {
  566. const toggleThemeBtn = '<li id="toggle_theme" class="toolbar_icon has_background" title="Toggle Editor Theme"></li>';
  567. const toggleSplitBtn = '<li id="toggle_split" class="toolbar_icon has_background" title="Toggle Split"></li>';
  568. const toggleSrcBtn = '<li id="show_source" class="toolbar_icon has_background" title="Toggle Source"></li>';
  569. const togglePreviewBtn = '<li id="show_preview" class="toolbar_icon has_background" title="Toggle Preview"></li>';
  570. const toggleHTMLBtn = '<li id="show_html" class="toolbar_icon has_background" title="Toggle HTML"></li>';
  571. const syncScrollEl = '<li id="sync_scroll"><input name="sync_scroll" type="checkbox"><label for="sync_scroll">Sync Scroll</label></li>';
  572. const clearTextBtn = '<li id="clear_text" title="Clear Text">Clear</li>';
  573. const saveBtn = '<li id="save_btn" title=""><ul class="menu has_popout_menu"><li id="save_text" class="border_right border_bottom" title="Save source text"><a target="_blank">Save Source</a></li><li id="save_HTML" class="border_right" title="Save rendered html"><a target="_blank">Save HTML</a></li></ul></li>';
  574. const toolbar = '<table id="toolbar"><tbody><tr><td><ul id="toolbar_buttons">'+ toggleThemeBtn + toggleSrcBtn + togglePreviewBtn + toggleSplitBtn + toggleHTMLBtn + syncScrollEl + saveBtn + clearTextBtn +'</ul></td></tr></tbody></table>';
  575. const textSource = '<textarea id="text_source" class="background_color_DD_33 text_color_111" tabindex="0"></textarea>';
  576. const textPreview = '<div id="text_preview" class="background_color_DD_33 text_color_111 markdown-body" tabindex="0"></div>';
  577. const htmlPreview = '<div id="html_preview" class="background_color_DD_33 text_color_111" tabindex="0"></div>';
  578. const textEditingUI = toolbar +'<div id="text_container">'+ textSource + textPreview + htmlPreview +'<div id="text_editing_handle"></div></div>';
  579. return textEditingUI;
  580. };
  581. // ASSEMBLE SIBEBAR & CONTENT PANE ELEMENTS
  582. const MainContent = function() {
  583. let width = Number(getQuery('width'));
  584. let handle = '<div id="handle"></div>';
  585. let toggle_sidebar = '<div id="toggle_sidebar" class="invert" title="Toggle Sidebar"></div>';
  586. let sidebar = '<div id="sidebar" class="background_color_C0_40">' + SidebarDirListEls() + SidebarFooterEls() +'</div>';
  587. let sidebar_wrapper = '<section id="sidebar_wrapper" class="border_right" style="width:'+ width +'%">'+ SidebarHeaderEls() + sidebar + handle + toggle_sidebar +'</section>';
  588. let content_pane = '<section id="content_pane" style="width:'+ ( 100 - width ) +'%">'+ ContentHeaderEls() + ContentEls() +'</section>';
  589. let utilities = '<section id="utilities"><div id="warnings_container" class="">'+ Warnings() +'</div><div id="help_container" class="background_color_E0_60">'+ $content_help +'</div></section>';
  590. let main_content = '<main id="main_content">'+ sidebar_wrapper + content_pane + utilities +'</main>';
  591. return $(main_content);
  592. };
  593. // DEFINE Content Elements
  594. const $main_content = MainContent();
  595. const $dir_list_body = $main_content.find('#tbody');
  596. const $dir_list = $main_content.find('#dir_list');
  597. const $content_pane = $main_content.find('#content_pane');
  598. const $audio_player = $main_content.find('#audio');
  599. // const $content_text = $main_content.find('#content_text');
  600. const $content_grid = $main_content.find('#content_grid');
  601. const $content_font = $main_content.find('#content_font');
  602. const $content_image = $main_content.find('#content_image');
  603. // const $content_pdf = $main_content.find('#content_pdf');
  604. const $content_video = $main_content.find('#content_video');
  605. const $content_iframe = $main_content.find('#content_iframe');
  606.  
  607. // SVG UI ICONS
  608. // auto format dark icon colr
  609. function SVG_UI_Icon(icon_name) {
  610. let svg = SVG_UI_Icons[icon_name];
  611. return 'url("data:image/svg+xml;utf8,'+ svg +'")';
  612. }
  613. const SVG_UI_Icons = {
  614. 'arrow': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M4 4l12 6-12 6z\' /></svg>',
  615. '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>',
  616. '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>',
  617. '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>',
  618. '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>',
  619. '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>',
  620. '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>',
  621. '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>',
  622. '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>',
  623. '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>',
  624. '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>',
  625. '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>',
  626. '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>',
  627. '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>',
  628. '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>',
  629. '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>',
  630. '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>',
  631. '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>',
  632. '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>',
  633. '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>',
  634. '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>',
  635. '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>',
  636. '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>',
  637. '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>',
  638. '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>',
  639. 'spinner': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 40 40\'><g><g transform=\'translate(1 1)\'><circle style=\'fill:none;stroke:%23666666;stroke-width:4;stroke-opacity:0.5;\' cx=\'18\' cy=\'18\' r=\'18\'/><path style=\'fill:none;stroke:%23AAAAAA;stroke-width:4;\' d=\'M36,18c0-9.94-8.061-18-18-18 \'><animateTransform type=\'rotate\' fill=\'remove\' repeatCount=\'indefinite\' attributeName=\'transform\' restart=\'always\' dur=\'1s\' from=\'0 18 18\' calcMode=\'linear\' to=\'360 18 18\' accumulate=\'none\' additive=\'replace\'></animateTransform></path></g></g></svg>',
  640. '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>',
  641. };
  642.  
  643. function SVG_UI_File_Icon(icon_name) {
  644. if ( icon_name === 'file_icon_dir_default' ) { // default chrome dir and file icons
  645. 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= ")';
  646. } else if ( icon_name === 'file_icon_file_default' ) {
  647. return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
  648. } else { // custom icons
  649. return 'url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons[icon_name] +'")';
  650. }
  651. }
  652. const SVG_UI_File_Icons = { // n.b.: order is important
  653. '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>',
  654. '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>',
  655. '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>',
  656. '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:%23999;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:%23999;\'/></svg>',
  657. '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>',
  658. '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>',
  659. '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:%23999;\'/></svg>',
  660. '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>',
  661. '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>',
  662. '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>',
  663. '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>',
  664. '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>',
  665. '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>',
  666. '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>',
  667. '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>',
  668. '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>',
  669. '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>',
  670. '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>',
  671. '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>',
  672. '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>',
  673. '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>',
  674. '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>',
  675. // the following are the same:
  676. '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>',
  677. '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>',
  678. '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>'
  679. };
  680. // Programatically add File icon CSS rules
  681. function CSS_UI_Icon_Rules() {
  682. let rules = '', kind, classname;
  683. rules += '#menu ul a::before { background-image:'+ SVG_UI_File_Icon('file_icon_file') +'; }';
  684. rules += '#menu ul a[href^="file"]::before { background-image:'+ SVG_UI_File_Icon('file_icon_dir') +'; }';
  685. rules += '#menu ul a[href^="http"]::before { background-image:'+ SVG_UI_File_Icon('file_icon_htm') +'; }';
  686. rules += '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; }';
  687. rules += '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; }';
  688. for ( let icon in SVG_UI_File_Icons ) {
  689. kind = icon.slice(icon.lastIndexOf('_') + 1);
  690. classname = kind;
  691. if ( kind === 'dirinvisible' ) { classname = 'dir.invisible'; }
  692. if ( kind === 'ignoredimage' ) { classname = 'ignored_image'; }
  693. // add rules for dir_list items, content_header, stats details:
  694. rules += 'body.use_custom_icons #dir_list tr.'+ classname +' a.icon span::before, #content_pane[data-content="has_'+ classname +'"] #title span::before, body.use_custom_icons #stats .stats_kind.'+ classname +' span.has_icon_before::before { background-image: url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons['file_icon_'+kind] +'"); }';
  695. }
  696. return rules;
  697. }
  698. // Text Editing UI Icons
  699. function SVG_Text_Editing_UI_Icon(icon_name) {
  700. let svg = SVG_Text_Editing_UI_Icons[icon_name];
  701. return 'url("data:image/svg+xml;utf8,'+ svg +'")';
  702. }
  703. const SVG_Text_Editing_UI_Icons = {
  704. '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>',
  705. '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>',
  706. '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>',
  707. '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>',
  708. '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>',
  709. '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>',
  710. '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>',
  711. '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>'
  712. };
  713. //***** END UI ELEMENTS
  714.  
  715. //***** STYLES *****//
  716.  
  717. // DEFINE STYLES
  718. const $warning_styles =
  719. // WARNINGS
  720. '#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; }' + // table
  721. 'body.has_warning #warnings_container { display:flex; }' +
  722. // warnings thead
  723. '#warnings_header { padding:1rem 1.5rem; background-position:left 1.25rem center; background-repeat:no-repeat; background-size:24px; }' +
  724. '#warnings_container:not(.warning_make_playlist) #warnings_header { background-image:'+ SVG_UI_Icon('error') +'; }' +
  725. '#warnings_header h3 { display:none; margin:0; text-indent:2.25em; }' +
  726. '#warnings_container:not(.warning_make_playlist) h3#warning_header, #warnings_container.warning_make_playlist h3#make_playlist_header { display:block;}' +
  727. 'body#top.edited #warnings_container.unloading h3::before { content: "Text Editor: " }' +
  728. // warnings tbody
  729. '#warnings .warning { padding: 0 1.5rem 1rem; display:none; hyphens:none; }' + // tbody
  730. // warnings tfoot
  731. '#warning_buttons_container { padding:1rem 1.5rem; }' +
  732. '#warning_buttons { display:flex; flex-direction:row; }' +
  733. '#warning_buttons button { min-width:4em; display:none; }' +
  734. 'button.focus, button:focus { background-color: #0E4399; color: #EEE; outline:none; }' +
  735. '#warning_btn_dont_save { margin-right: auto; }' + // button
  736. '#warning_btn_cancel, #warning_btn_clear, #warning_btn_save { margin-left: 0.5rem; }'+ // button
  737. '#warning_btn_ok { margin-left:auto; }'+ // button
  738. '#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; }'+ // button
  739. '#warnings_container.clear #warning_buttons { justify-content:space-between; }'+
  740. '#warnings_container.clear #warning_clear_text, #warnings_container.clear #warning_btn_cancel, #warnings_container.clear #warning_btn_clear { display:inline-block; }'+
  741. '#warnings_container.local_bookmark #warning_local_bookmark, #warnings_container.local_bookmark #warning_btn_ok { display:inline-block; margin-left:auto; }'+
  742. '#warnings_container.warning_close_font #warning_btn_ok { display:inline-block; margin-left:auto; }'+
  743. '#warnings_container.warning_close_font #warning_btn_cancel { display:inline-block; }'+
  744. '#warnings_container.warning_close_playlist #warning_btn_ok { display:inline-block; margin-left:auto; }'+
  745. '#warnings_container.warning_close_playlist #warning_btn_cancel { display:inline-block; }'+
  746. '#warning_make_playlist fieldset { margin:0; padding:0; border:0; }'+
  747. '#warning_make_playlist fieldset div { padding:0 0 2px; }'+
  748. '#warning_make_playlist .indent { text-indent:2em; }'+
  749. '#warning_make_playlist input { margin-right:6px; }'+
  750. '#warnings_container.warning_make_playlist #warning_make_playlist { display:flex; flex-direction:column; }'+
  751. '#warnings_container.warning_make_playlist #warning_btn_ok, #warnings_container.warning_make_playlist #warning_btn_cancel { display: inline-block; }' +
  752. '#warnings_container.warning_no_playlist #warning_no_playlist, #warnings_container.warning_no_playlist #warning_btn_ok { display: inline-block; }' +
  753. '#warnings_container.warning_local_playlist #warning_local_playlist, #warnings_container.warning_local_playlist #warning_btn_ok { display:inline-block; margin-left:auto; }'
  754. ;
  755. var $main_styles =
  756. ':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; }' +
  757. // 'body + div { top:-1em !important; }' + // for vivaldi picture-in-picture div after body
  758. 'table { width:100%; border:0; border-collapse: collapse; }' +
  759. '#sidebar_wrapper li, #toolbar li { list-style:none; }' + //
  760. 'a, a:hover { text-decoration: none !important; }' +
  761. 'button { padding:2px 6px; border:solid 1px #333; border-radius:3px; cursor:pointer; height:18px; line-height:0; }' +
  762. 'button:focus, textarea:focus, audio:focus { outline:none; }' +
  763.  
  764. '#main_content { width:100%; display:flex; flex-direction:row; overflow:hidden; }' + // table with initial height
  765. '#handle { position:absolute; top:0; bottom:0; z-index:1; cursor:col-resize; right:-4px; width:7px; }' +
  766. // OVERLAY
  767. 'body.has_overlay #handle { z-index:9999; }' +
  768. 'body.has_warning::before, body.has_overlay::before { content:""; position:absolute; top:0; right:0; bottom:0; left:0; z-index:9998; -webkit-user-select:none; -moz-user-select:none; user-select:none; }' +
  769. // WARNING STYLES
  770. $warning_styles +
  771. // HELP STYLES
  772. '#help_container { display:none; padding: 0 1em 1em; overflow:scroll; position:absolute; top:0; right:0; bottom:0; left:0; z-index:9998; contain:strict; }' + // tr
  773. '#help_container header { font-size:0.875rem; text-align:center; margin-right:calc(-1em - 2px); margin-left:calc(-1em - 2px); }' + //
  774. '#help_container header > span { display:inline-block; padding:6px; font-weight:bold; }' + //
  775. '#help_container tr { display:flex; border-bottom:solid 1px #666; }' + // td
  776. '#help_container td { vertical-align:top; }' + // td
  777. '#content_help { margin:1em auto; width:auto; overflow:auto; }' + // table
  778. '#close_help { float:right; margin-left:-100% !important; margin:4px 6px; }' + //
  779. '#content_help tbody { font-size:0.875rem; padding:8px; overflow:auto; }' + //
  780. '#content_help td.kbd_shortcut { text-align:right; width:33%; padding:4px 9px 4px 6px; }' + // td
  781. '#content_help td.help_description { width:66%; padding:4px 6px 4px 12px; }' + // td
  782. '#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; }' +
  783.  
  784. //***** SIDEBAR STYLES *****//
  785. '#sidebar_wrapper { min-width:220px; padding:0; position:relative; z-index:1; display:flex; flex-direction:column; }' +
  786. '#sidebar { overflow:hidden; font-size:0.875rem; display:flex; flex-direction:column; flex-basis:100%; }' +
  787. '#sidebar_header { font-size:0.875rem; position:relative; z-index:3; user-select:none; -webkit-user-select:none; display:flex; flex-direction:column; }' +
  788. '#sidebar_title { font-weight:normal; display:flex; flex-direction:row; }' +
  789. '#sidebar_title div { padding: 4px 6px; text-align:center; letter-spacing:0.5em; text-indent:0.75em; flex-basis:100%; }' +
  790. '#sidebar_header_body { display:flex; flex-direction:column; }' +
  791. '#toggle_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; }' +
  792. '#sidebar ul { -webkit-margin-before:0em !important; -webkit-margin-after:0em !important; -webkit-padding-start:0em; }' +
  793. '#sidebar_menus { cursor:pointer; display:flex; flex-direction:row; }' +
  794. // PARENT MENU
  795. '#parent_dir_menu { flex-basis:24px; max-width:24px; min-width:24px; padding:0; position:relative; }' +
  796. '#parent_dir_nav { margin:0; padding:0; display:block; position:absolute; top:0; right:0; bottom:0; left:0; }' +
  797. '#parent_dir_nav a { height:100%; padding:0; text-align:center; background-position:center; background-repeat:no-repeat; background-size:12px; }' +
  798. // PARENTS MENU
  799. '#parents_dir_menu { padding:0; flex-grow:1; }' +
  800. '#parents_dir_nav { margin:0; padding:0; display:block; line-height:1.4; }' + // nav
  801. '#current_dir_path { cursor:pointer; font-weight:bold; hyphens:none; padding:4px 6px 4px 6px; text-align:center; z-index:9998; }' +
  802. '#parents_links { margin:0; padding:0; display:none; position:absolute; right:0; left:0; box-shadow: 0px 4px 6px -3px #333; z-index:9998; }' + // ul
  803. '#parents_links a { margin:0; padding:4px 8px; display:block; }' +
  804. // MAIN MENU
  805. '#menu_container { display:flex; flex-direction:column; justify-content:center; flex-basis:24px; max-width:24px; min-width:24px; padding:0; }' +
  806. '#menu_nav { margin:0; padding:0; display:block; cursor:pointer; }' + // nav
  807. '#menu_nav div { width:24px; background-image:'+ SVG_UI_Icon('menu') + '; background-position:center; background-repeat:no-repeat; background-size:13px; }' +
  808. '#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; }' + // ul
  809. '#menu li.has_submenu { position:relative; background-position:right 6px center; background-repeat:no-repeat; background-size:12px; }' + // right triangle icon
  810. '#menu li.bookmark a::before { content:""; width:24px; height: 12px; background-size:12px; }' + // bookmark icon
  811. '.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; }' + // ul
  812. '#menu ul.submenu li a { margin:0; padding:6px 8px 6px 0; }' +
  813. '#menu input { width:0; float:left; }' +
  814. '.menu_item { margin:0; padding:6px 8px 6px 0; display:flex; }' +
  815. '.menu_item::before { content:""; width:24px; height:9px; margin:2px 0 -2px; display:inline-block; background-position:center; background-repeat:no-repeat; }' +
  816. // SIDEBAR BUTTONS
  817. '#sidebar_buttons { position:relative; display:flex; flex-direction:row; }' +
  818. '#sidebar_buttons_left { padding:6px; }' +
  819. '#show_details { margin-top:0; margin-right:0.5em; padding:0 4px 0 4px; }' +
  820. '#show::before { content:"Show "; }' +
  821. '#show_invisibles_container { cursor:pointer; }' +
  822. '#show_invisibles { cursor:pointer; }' +
  823. '#show_invisibles:hover span { font-weight:bold; }' +
  824. // Grid Btn
  825. '#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; }' +
  826. '#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; }' +
  827. '#grid_btn ul li { padding:4px 6px; text-align:right; width:100%; white-space:pre; box-sizing:border-box; }' +
  828. // SORTING ITEMS
  829. '#sorting_row_1 { display:flex; cursor:pointer; justify-content:space-between; flex-direction:row; }' +
  830. '#sorting_row_2 { display:none; cursor:pointer; justify-content:space-between; flex-direction:row; }' +
  831. '#sorting_row_1 div { width:50%; }' +
  832. '#sorting_row_2 div { width:25%; }' +
  833. '#sorting_row_1 div, #sorting_row_2 div { flex-grow:1; padding:0; }' +
  834. '#sorting .sorting { white-space:pre; }' +
  835. '#sorting_row_1 span { display:inline-block; padding:6px 0; }' +
  836. '#sorting_row_2 span { display:inline-block; padding:0 0 6px 0; }' +
  837. '.sorting:hover span { font-weight:bold; }' + //
  838. '.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; }' +// content
  839. '.sorting.down span::after { transform:rotate(180deg) }' +
  840. '#sort_by_name input { display:none; margin:0 4px 0 2px; }' +
  841. '#sort_by_name, #sort_by_size { text-align:left; }' +
  842. '#sort_by_date, #sort_by_kind { text-align:center; }' +
  843. '#sort_by_default, #sort_by_ext { text-align:right; }' +
  844. '#sorting div.up span::after { transform:rotate(180deg) !important; }' +
  845. // TEXT EDITOR ROW
  846. '#text_editor_row { display:none; }' +
  847. '#text_editor_row a { padding:6px 0 6px 16px; text-align:left; font-weight:bold; }' +
  848.  
  849. //***** DIR_LIST STYLES *****//
  850. '#dir_list_wrapper { overflow:scroll; position:relative; }' +
  851. '#dir_list { overflow:hidden; position:relative; font-size:0.875rem; }' +
  852. '#tbody { counter-reset:row; height:100%; overflow:hidden; transition:opacity .125s; background-position:center; background-repeat:no-repeat; background-size:50%; }' +
  853. '#tbody > tr { margin-inline-start:0; display:grid; grid-gap:0; grid-template-columns:minmax(auto,5rem) 1fr minmax(auto,7em); }' + // tr
  854. '.tbody_row_cell { padding:0; }' + // td
  855. '.tbody_row_cell_name { grid-column: 1 / span 3; font-variant-numeric:tabular-nums; }' +
  856. '.tbody_row_cell_name_a { display:flex; text-decoration:none; -webkit-padding-start:0; -moz-padding-start:0; }' + // td.name a.icon
  857. '.tbody_row_cell_name_a::before { display:none; counter-increment: row; content:counter(row); width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; text-align:right; padding-right:1px; }' + // numbers
  858. '.tbody_row_cell_name_a_span { display:flex; line-height:1.4; text-align:left; word-break:break-word; }' + // req: .has_background_before
  859. '.tbody_row_cell_name_a_span input { display:none; margin:2px 6px 0 0; }' + // media checkboxes
  860. '.tbody_row_cell_details { display:none; text-align:right; white-space:pre; max-height:1em; font-variant-numeric:tabular-nums; }' +
  861. '.tbody_row_cell_details.size { grid-column: 1; grid-row: 2; padding:0 8px 3px 8px; }' +
  862. '.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; }' +
  863. '.tbody_row_cell_details.date span { white-space:pre; display:inline-block; }' +
  864. '.tbody_row_cell_details.kind { grid-column: 3; grid-row: 2; overflow:hidden; text-overflow:ellipsis; }' +
  865. '.tbody_row_cell_name_a { padding:5px 8px 5px 0; }' + //
  866. '.tbody_row_cell_name_a::before { margin-top:1px; }' + //
  867. '.tbody_row_cell_details { padding:0 16px 3px 0; }' + // padding:4px 8px 4px 0;
  868.  
  869. 'tr:hover a, tr.selected a, tr.playing a { font-weight:bold; }' +
  870. 'tr.disabled, tr.disabled a, tr.disabled span, tr.ignore, tr.ignore a { cursor:not-allowed; }' +
  871. 'tr.invisible { display:none; }' +
  872. // PLAYLIST ITEMS
  873. 'body[class$="list"] #tbody tr { display: grid; grid-gap:0; grid-template-columns: 48px minmax(8rem,18rem) minmax(auto,8rem); }' +
  874. 'body[class$="list"] #tbody td.name { grid-column: 1 / span 4; }' +
  875. 'body[class$="list"] #tbody td.size { grid-column: 1 / span 2; grid-row: 2; padding:0 8px 6px 8px; text-align:left; text-indent: 40px; }' +
  876. 'body[class$="list"] tbody td.kind { grid-column: 3 / span 2; grid-row: 2; padding:0 8px 6px 8px; }' +
  877. 'body[class$="list"] #tbody td.details.ext { display:none; }' +
  878. 'body.show_numbers[class$="list"] #tbody td.size { text-indent: 48px; }' +
  879.  
  880. //***** SIDEBAR FOOTER *****//
  881. '#tfoot { margin-top:auto; padding:0; position:relative; font-size:0.875rem; user-select:none; -webkit-user-select:none; display:flex; flex-direction:column; }' +
  882. '#tfoot:hover { box-shadow: 0px 4px 6px 3px #333; }' + // tr
  883. // STATS
  884. '#stats { cursor:pointer; float:left; width:100%; overflow:hidden; flex-direction:column; line-height:1.6; font-size:0.875rem; }' + // td
  885. '#stats_summary, #stats_summary_playlist_container { padding:2px 8px; overflow:hidden; white-space:pre; }' + // tr
  886. '#stats_summary div > div { padding:0 6px; }' + // tr
  887. '#stats_summary_playlist_container, #stats_summary_detailed_container, #stats_details_container { display:none; flex-direction:column; }' + // tbody
  888. '#stats_summary_playlist_container > div, #stats_summary_detailed_container > div, #stats_details_container > div { flex-direction:row; }' + // tbody
  889. '.summary_detailed { display:none; }' +
  890. '.stats_count { display:inline-block; padding:2px 2px 2px 6px; text-align:right; min-width: 22px; }' +
  891. '.stats_kind { display:inline-block; padding:2px 6px 2px 0; }' +
  892. '#stats_summary_detailed_total { padding-left:4px; font-weight:bold; }' +
  893. '#stats_summary_detailed_dirs span { padding-bottom:0; }' +
  894. '#stats_summary_detailed_dirs .stats_kind::before { background-image:'+ SVG_UI_File_Icon('file_icon_dir_default') +'; }' +
  895. '#stats_summary_detailed_files .stats_kind::before { background-image:'+ SVG_UI_File_Icon('file_icon_file_default') +'; }' +
  896. '#stats_details_container > div:first-of-type span { padding-top:4px; }' +
  897. '#stats_details_container > div:last-of-type span { padding-bottom:4px; }' +
  898. '#stats_summary_detailed_dirs .stats_kind, #stats_summary_detailed_files .stats_kind { display:flex; }' +
  899. '#stats_summary_detailed_dirs:hover, #stats_summary_detailed_files:hover, #stats_details_container div:hover { font-weight:bold; }' +
  900. // SIDEBAR FOOTER LINKS
  901. '#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; }' + // td
  902. '#footer_links ul { display:none; padding:0; position:absolute; top:calc(100% - 1px); right:unset; left:calc(100% - 27px); white-space:pre; box-shadow:-0px -3px 6px -3px #333; transform:rotate(180deg) !important; }' + // ul
  903. '#open_in_content_pane { padding:4px 6px; }' +
  904. '#view_directory_source { padding:4px 6px; }' +
  905.  
  906. // CLASSES AND ELEMENTS
  907. '.has_icon_before::before { content:""; display:block; float:left; 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; }' +
  908. '.has_checkmark_before::before { content:""; height:9px; background-position:center; background-repeat:no-repeat; }' +
  909. 'ul.has_popout_menu, .editor_theme_light #toolbar ul.has_popout_menu { background-color:#C0C0C0; border:solid 1px #666; }' +
  910. 'ul.has_popout_menu li { background-color:#D0D0D0; }' +
  911. 'ul.has_popout_menu li:hover { background-color:#E0E0E0; }' +
  912. '.theme_dark #sidebar_wrapper ul.has_popout_menu { border:solid 1px #111; }' +
  913. '.theme_dark #sidebar_wrapper ul.has_popout_menu, .theme_dark #sidebar_wrapper ul.has_popout_menu li { background-color:#505050; }' +
  914. '.editor_theme_dark #toolbar ul.has_popout_menu li { background-color:#C0C0C0; }' +
  915. '.theme_dark #sidebar_wrapper ul.has_popout_menu li:hover { background-color:#686868; }' +
  916. //***** END SIDEBAR STYLES *****//
  917.  
  918. //***** CONTENT STYLES *****//
  919. '#content_pane { height:100%; padding:0; position:relative; transform:scale(1); vertical-align:top; display:flex; flex-direction:column; contain:strict; }' + // td
  920. '#content_header { display:block; font-size:0.875rem; position:relative; z-index:3; }' + // header
  921. '#content_header table { font-size:0.875rem; z-index:2; }' +
  922. '#content_title { text-align:center; display:flex; flex-direction:row; justify-content:space-between; }' + // tr
  923. // CONTENT TITLE BUTTONS LEFT
  924. '#title_buttons_left { padding:4px 6px; text-align:left; width:4rem; max-width:9.5em; vertical-align:top; }' + // td
  925. '#reload_btn { float:left; width:52px; }' +
  926. '#reload_btn::before { content:"Reload"; }' +
  927. '#prev_next_btns { margin-left:8px; cursor:pointer; float:left; line-height:1; padding:0; position:relative; }' + // button
  928. '#prev_next_btns span { width:2em; height:18px; }' +
  929. '#prev_btn, #next_btn { background-position:center 36%; background-repeat:no-repeat; background-size:33%; }' +
  930. '#prev_btn { background-image:'+ SVG_UI_Icon('chevron_left') + '; }' +
  931. '#next_btn { background-image:'+ SVG_UI_Icon('chevron_right') + '; }' +
  932. // CONTENT TITLE
  933. '#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; }' + // td
  934. '#title span { font-weight:bold; }' +
  935. '#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; }' +
  936. '#title span::after { font-weight:bold; }' +
  937. '#content_pane[data-content="has_image"] #title span::after { font-weight:normal; }' +
  938. // CONTENT TITLE BUTTONS RIGHT
  939. '#title_buttons_right { padding:4px 6px; text-align:right; width:4rem; max-width:9.5em; vertical-align:top; }' + // td
  940. '#scale { cursor:pointer; float:right; line-height:1; margin-right:8px; padding:0 4px; position:relative; background-color:#FFF; }' + // button
  941. '#scale span { width:2em; height:18px; background-position:center 36%; background-repeat:no-repeat; background-size:10px; }' +
  942. '#decrease { margin-left:-4px; background-image:'+ SVG_UI_Icon('minus') +'; }' +
  943. '#increase { margin-right:-4px; background-image:'+ SVG_UI_Icon('plus') +'; }' +
  944. '#close_btn { float:right; padding:0px; width:52px; }' + // close button
  945. '#close_btn::before { content:"Close"; }' + //
  946. '.split_btn { display:none; }' +
  947. '.split_btn span { display:inline-flex; }' +
  948. '.split_btn::after { content:""; position: absolute; top: 0; bottom: 0; left: 50%; }' +
  949. // CONTENT AUDIO TITLE
  950. '#content_audio_title { display:none;}' + // tr
  951. '#content_audio_title span { display:block; width:100%; cursor:pointer; padding:4px 6px 6px; text-align:center; line-height:1.4; }' + // td
  952. '#content_audio_title span::before { content:""; padding-right:18px; font-weight:normal; background-position:center; background-position:right 4px center; background-repeat:no-repeat; }' + // td
  953. // CONTENT AUDIO PLAYER
  954. '#content_audio { display:none; justify-content:center; padding-bottom:6px; }' + // tr
  955. '#content_audio > div > div { text-align:center; font-weight:bold;}' + // td
  956. '#audio_container { padding:0 4px; height:32px; display:flex; background-color:rgb(241, 243, 244); flex-direction:row; }' + // div; background-color is for chrome browsers
  957. '#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; }' + // div
  958. '#prev_track { transform:rotate(180deg) !important; }' + // div
  959. '#audio { height:32px; }' + // audio
  960. // 'body:not(.is_gecko) #audio { margin-top: -9px; }' + // hacky fix for PIP button in Vivaldi
  961. '#close_audio { width:2rem; padding:0; position:relative; display:inline-block; background-image:'+ SVG_UI_Icon('multiply') +'; background-position:center; background-repeat:no-repeat; background-size:14px; }' + // div , .audio_controls, #glyph_viewer_info div
  962. '#audio_options { margin-top:0; margin-right:calc(-6em - 8px); padding:0 4px; width:6em; display:flex; flex-direction:column; justify-content:center; }' + // div
  963. '#loop_label input { margin:0px 4px 2px}' + // input
  964. '#shuffle_label input { margin:2px 4px 0px}' + // input
  965. '#audio_iframe { margin:0; padding:0; border:0; }' +
  966. // CONTENT TITLE PLAYLIST ENTRY (#content_playlist and #content_audio_playlist)
  967. '.playlist_entry_container { display:none; padding:4px 6px; text-align:center; flex-direction:row; }' + // td
  968. '.playlist_entry_container textarea { width:100%; padding:0 6px; border:0; resize:vertical; }' + // tr
  969.  
  970. // CONTENT_CONTAINER (section)
  971. '#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; }' + // section
  972. '.content { display:none; overflow:scroll; width:100%; height:100% }' + // hide content by default
  973. // CONTENT GRID (div)
  974. '#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); }' +
  975. '#content_grid::after { content:""; width:1px; position:absolute; top:0; right:0; bottom:0; }' +
  976. '#content_grid a { display:block; }' +
  977. // Image Grid Items
  978. '.image_grid_item { padding:6px; grid-column:auto; display:flex; align-items:center; justify-content:center; line-height:0; }' +
  979. '.image_grid_item img { width:auto; max-width:'+ ($settings.grid_image_size).toString() +'px; max-height:'+ ($settings.grid_image_size) +'px; position:relative; }' +
  980. '.image_grid_item img[src$=".svg"] { width: 100%; height:100%; }' +
  981. // Font Grid Items
  982. '.font_grid_item { line-height:1; padding:8px 20px; grid-column: 1 / -1; }' +
  983. '.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; }' +
  984. '.font_grid_item h2 { margin:0; font-weight:normal; font-size:'+ $settings.grid_font_size * 4 +'em; }' +
  985. '.image_grid_item + .font_grid_item { margin-top:-1px; }' + // mask double borders
  986. // CONTENT TEXT EDITOR
  987. '#content_text { display:none; width:100%; max-width:100%; height:100%; overflow:hidden; padding:0; position:absolute; z-index:1; flex-direction:column; }' + // div
  988. // CONTENT FONT.content
  989. '#content_font { hyphens:none; padding:0; position:relative; font-size:'+ $settings.grid_font_size +'em; overflow-wrap:break-word; }' + // div
  990. '#font_specimen { max-width:100%; display:none; line-height:1.2; flex-direction:column; }' + // div
  991. '#specimen { padding: 20px; font-size:4em; word-break:break-all; line-height:1.2; }' + // div
  992. '#specimen_2 { margin:0; font-weight:normal; font-size:8em; overflow:hidden; text-overflow:ellipsis; white-space:pre; }' +
  993. '#specimen_2H4 { margin:0; font-weight:normal; font-size:1.618em; overflow:hidden; text-overflow:ellipsis; }' +
  994. '#specimen_3 { margin:0; font-weight:normal; font-size:6em; text-align:justify; overflow:hidden; text-overflow:ellipsis; white-space:pre; }' +
  995. '#specimen_3H3 { margin:0; font-size:2em; hyphens:auto; }' +
  996. '#specimen_string_2 { padding:20px; text-align:justify; hyphens:auto; }' + // div
  997. '#specimen_string_3 { padding:20px; text-align:justify; }' + // div
  998. '.lorem { text-align:justify; hyphens:auto; font-size:1em; line-height:1.4; column-gap:1.5em; overflow-wrap:normal; word-break:normal; }' + // div
  999. '#lorem { padding:20px 20px 0; }' + // div
  1000. '#lorem::first-line { letter-spacing:0.1em; text-indent:0.1em; font-size:'+ $settings.grid_font_size * 1.33 +'em; font-variant:small-caps; }' +
  1001. '#lorem_2 { padding:12px 20px 0; columns:2; }' + // div
  1002. '#lorem_3 { padding:12px 20px 40px; columns:3; }' + // div
  1003. // FONT GLYPHS
  1004. '#font_viewer { display:none; position:relative; font-family:unset; width:100%; }' + // div
  1005. '#content_pane[data-content="has_font_file"] #font_viewer { overflow-y:auto; }' + // div
  1006. '#content_pane[data-content="has_glyph"] #font_viewer { overflow:auto; }' +
  1007. '#content_pane[data-content="has_glyph"] #glyphs_container { visibility:hidden; }' +
  1008. '#glyphs_container { padding:0; position:relative; text-align:center; display:grid; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(120px,auto)); }' + // div
  1009. '.glyph_container { padding:0; position:relative; }' + // div
  1010. '.glyph_info { padding:2px; position:absolute; right:0; bottom:0; left:0; font-size:0.75rem; }' +
  1011. '#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; }' + // div
  1012. '#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; }' + // div
  1013. '#content_font svg { width: 100%; }' +
  1014. '#save_svg_hidden { float:left; visibility:hidden; }' + // button
  1015. '#glyph_viewer_info div { padding:0; display:inline-block; }' + // button
  1016. '#glyph_viewer_info div::before { content:"Glyph "; }' +
  1017. '#save_svg { float:right; }' + // button
  1018. '#font_info { max-height:'+ (window.innerHeight * 0.75) +'px; font-size:0.875rem; position:fixed; bottom:0; z-index:2; }' +
  1019. '#font_info:hover { box-shadow:0px 4px 6px 3px #333; }' + // tr
  1020. '#font_info th { padding:4px 6px 5px; letter-spacing:0.1em; text-indent:0.1em; }' +
  1021. '#font_info_body { display:none; max-height:'+ ((window.innerHeight * 0.75) - 64) +'px; font-size:0.875rem; overflow:auto; }' +
  1022. '.font_info_name { padding:4px 6px; text-align:right; font-weight:bold; width:33%; }' +
  1023. '.font_info_value { padding:4px 6px; width_66%; }' +
  1024. '.font_info_value a { font-weight:bold; }' +
  1025. // CONTENT IMAGE.content
  1026. '#content_image { display:none; margin:0; padding:2rem 2.5rem; position:relative; overflow:auto; box-sizing:border-box; }' + // div
  1027. '#content_image img { margin:auto; width:auto; max-width:100%; max-height:100%; position:relative; object-fit:contain; cursor:zoom-in; }' + // img
  1028. '#content_pane.has_zoom_image #content_image { padding:0; }' + // div
  1029. // OTHER CONTENT ELEMENTS
  1030. '#content_pdf { height:100%; padding:0; position:relative; width:100%; }' + // embed.content
  1031. '#content_video { padding:0; position:absolute; background:transparent; }' + // video.content
  1032. '#content_iframe { border:0; height:100%; padding:0; position:relative; width:100%; background-color: #FFF; }' // iframe.content
  1033. ;
  1034. var $color_and_background_styles = // added to #top and #iframe_body
  1035. // BACKGROUND COLORS
  1036. // 'body.theme_light .background_color_A0_20 { background-color: #A0A0A0; }' +
  1037. // 'body.theme_dark .background_color_A0_20 { background-color: #202020; }' +
  1038. 'body.theme_light, body.theme_light .background_color_B0_30 { background-color: #B0B0B0; }' +
  1039. 'body.theme_dark, body.theme_dark .background_color_B0_30 { background-color: #303030; }' +
  1040. 'body.theme_light .background_color_C0_40 { background-color: #C0C0C0; }' +
  1041. 'body.theme_dark .background_color_C0_40 { background-color: #404040; }' +
  1042. 'body.theme_light .background_color_D0_50 { background-color: #D0D0D0; }' +
  1043. 'body.theme_dark .background_color_D0_50 { background-color: #505050; }' +
  1044. 'body.theme_light .background_color_E0_60 { background-color: #E0E0E0; }' +
  1045. 'body.theme_dark .background_color_E0_60 { background-color: #606060; }' +
  1046.  
  1047. 'body.theme_light .background_color_DD_44 { background-color: #DDDDDD; }' + // tbody background
  1048. 'body.theme_dark .background_color_DD_44 { background-color: #383838; }' +
  1049. 'body.theme_light:not(.editor_theme_dark) .background_color_DD_33, body.editor_theme_light .background_color_DD_33 { background-color: #DDDDDD; }' +
  1050. 'body.theme_dark:not(.editor_theme_light) .background_color_DD_33, body.editor_theme_dark .background_color_DD_33 { background-color: #333333; }' +
  1051. 'body.theme_light .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 { background-color: #EEEEEE; }' +
  1052. '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 { background-color: #222222; }' +
  1053. 'body.theme_light .background_color_FF_11 { background-color: #EFEFEF; }' +
  1054. 'body.theme_dark .background_color_FF_11 { background-color: #0F0F0F; }' +
  1055. '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; }' +
  1056. '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; }' +
  1057. 'body.theme_light .background_color_22_EE { background-color: #222222; }' +
  1058. 'body.theme_dark .background_color_22_EE { background-color: #EEEEEE; }' +
  1059. '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; }' +
  1060. '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; }' +
  1061.  
  1062. // DIR LIST ROWS: .alternate_background
  1063. 'body.theme_dark.alternate_background #tbody tr:nth-of-type(odd) { background-color: #505050; }' +
  1064. 'body.theme_light.alternate_background #tbody tr:nth-of-type(odd) { background-color: #D0D0D0; }' +
  1065. 'body.theme_dark.alternate_background #tbody tr:nth-of-type(even) { background-color: #404040; }' +
  1066. 'body.theme_light.alternate_background #tbody tr:nth-of-type(even) { background-color: #E8E8E8; }' +
  1067. 'body.theme_dark #content_help tr:nth-of-type(even) { background-color: #484848; }' +
  1068. 'body.theme_light #content_help tr:nth-of-type(even) { background-color: #CCCCCC; }' +
  1069. // NON-MEDIA ROWS .selected, .loaded, :hover ("light cyan")
  1070. 'body.theme_light tr:not(.media).selected, body.theme_light tr:not(.media).selected.loaded, body.theme_light tr.selected.loaded:hover, body.theme_light #menu li.selected'+
  1071. '{ background-color: rgba(172,202,235,1.00) !important; }' +
  1072. 'body.theme_light tr:not(.media).loaded, body.theme_light #tbody tr:not(.media):hover, body.theme_light.alternate_background #tbody tr:not(.media):hover, body.theme_light tr:not(.media).hovered'+
  1073. '{ background-color: rgba(172,202,235,0.60) !important; }' +
  1074. 'body.theme_dark tr:not(.media).selected, body.theme_dark tr:not(.media).selected.loaded, body.theme_dark tr.selected.loaded:hover, body.theme_dark #menu li.selected'+
  1075. '{ background-color: rgba(101,140,179,0.80) !important; }' + //#658CB3
  1076. 'body.theme_dark tr:not(.media).loaded, body.theme_dark #tbody tr:not(.media):hover, body.theme_dark.alternate_background #tbody tr:not(.media):hover, body.theme_dark tr:not(.media).hovered'+
  1077. '{ background-color: rgba(101,140,179,0.60) !important; }' +
  1078. // MEDIA ROWS .playing, .selected, :hover
  1079. 'body.theme_light tr.media.playing { background-color: rgba(130,196,196,1) !important; }' + // #82C4C4
  1080. 'body.theme_light tr.media.selected:not(.playing), body.theme_light tr.media.hovered' +
  1081. '{ background-color: rgba(116,190,190,0.60) !important; }' +
  1082. 'body.theme_light tr.media:not(.playing):hover { background-color: rgba(116,190,190,0.40) !important; }' +
  1083. 'body.theme_dark tr.media.playing { background-color: rgba(076,143,143,0.75) !important; }' +
  1084. 'body.theme_dark tr.media.selected:not(.playing), body.theme_dark tr.media.hovered' +
  1085. '{ background-color: rgba(076,143,143,0.55) !important; }' +
  1086. 'body.theme_dark tr.media:hover { background-color: rgba(076,143,143,0.45) !important; }' +
  1087. // text editor row ("purple")
  1088. '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
  1089. '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
  1090. // menu items :hover, content_iframe
  1091. 'body.theme_dark .menu li:hover { background-color: #686868; }' +
  1092. 'body.theme_light .menu li:hover { background-color: #B8B8B8; }' +
  1093.  
  1094. // BACKGROUND IMAGES
  1095. '.has_background, .has_background_before::before, .has_background_after::after { background-repeat:no-repeat; background-position:center; background-color:transparent !important; }' +
  1096. 'body.theme_light .bookmark > a::before { background-image: '+ SVG_UI_Icon('bookmark') +'; }' +
  1097. 'body.theme_dark .bookmark > a::before { background-image: '+ SVG_UI_Icon('bookmark_dark') +'; }' +
  1098. 'body.theme_light li.has_submenu { background-image: '+ SVG_UI_Icon('arrow') +'; }' +
  1099. 'body.theme_dark li.has_submenu { background-image: '+ SVG_UI_Icon('arrow_dark') +'; }' +
  1100. '.background_color_check_mark::before, #theme::before, body.sort_by_default #default span::before, body.sort_by_name #name 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.split_view #split_view_menu_item, body.split_view #split_view span::before, body.source_text:not(.preview_text) #source_text::before, body.preview_text:not(.source_text) #preview_text::before, body.sort_by_default #sort_by_default span::before, body.sort_by_name #sort_by_name 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.ignore_ignored_files #ignore_ignored_files span::before, #disable_text_editing::before'+
  1101. '{ background-image:'+ SVG_UI_Icon('check_mark') +'; }' +
  1102. 'body.sort_by_default #sort_by_default span::after, body.sort_by_name #sort_by_name 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'+
  1103. '{ background-image:'+ SVG_UI_Icon('chevron_down') +'; }' +
  1104. '#parent_dir_nav a { background-image:'+ SVG_UI_Icon('chevron_up') +'; }' +
  1105. 'body.is_error #tbody { background-image: '+ SVG_UI_Icon('error') +'; }' +
  1106. 'body.theme_light #content_pane[data-content="has_ignored"] #content_container'+
  1107. '{ background-image:'+ SVG_UI_Icon('ignored') +'; }' +
  1108. 'body.theme_dark #content_pane[data-content="has_ignored"] #content_container'+
  1109. '{ background-image:'+ SVG_UI_Icon('ignored_dark') +'; }' +
  1110. 'body.has_audio #content_pane:not([data-content^="has_"]) #content_container'+
  1111. '{ background-image:'+ SVG_UI_Icon('music') +'; }' +
  1112. CSS_UI_Icon_Rules() +
  1113. 'body.theme_light #grid_btn:not(.has_grid), body.theme_light #grid_btn:not(.has_grid) .menu'+
  1114. '{ background-image:'+ SVG_UI_Icon('grid') +'; }' +
  1115. 'body.theme_dark #grid_btn:not(.has_grid), body.theme_dark #grid_btn:not(.has_grid) .menu'+
  1116. '{ background-image:'+ SVG_UI_Icon('grid_dark') +'; }' +
  1117. 'body.theme_light #grid_btn.has_grid, body.theme_light #grid_btn.has_grid .menu'+
  1118. '{ background-image:'+ SVG_UI_Icon('grid_loaded') +'; }' +
  1119. 'body.theme_dark #grid_btn.has_grid, body.theme_dark #grid_btn.has_grid .menu'+
  1120. '{ background-image:'+ SVG_UI_Icon('grid_loaded_dark') +'; }' +
  1121. '#content_pane[data-content="has_text_editor"] #title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_markdown') +'; }' +
  1122. '#content_pane[data-content="has_font_file"] #title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_font') +'; }' +
  1123. '#content_audio_title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_audio') +'; height:14px !important; }' +
  1124. '#content_pane[data-content="has_grid"] #title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_dir') +'; height:14px !important; }' +
  1125. '#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; }' +
  1126.  
  1127. // BORDERS
  1128. 'body.theme_dark .border_all { border: solid 1px #111; }' +
  1129. 'body.theme_light .border_all { border: solid 1px #666; }' +
  1130. 'body.theme_dark .border_top { border-top: solid 1px #111; }' +
  1131. 'body.theme_light .border_top { border-top: solid 1px #666; }' +
  1132. 'body.theme_dark .border_right { border-right: solid 1px #111; }' +
  1133. 'body.theme_light .border_right { border-right: solid 1px #666; }' +
  1134. 'body.theme_dark .border_bottom, body.theme_dark #tbody tr:last-of-type:not(.invisible) { border-bottom: solid 1px #111; }' +
  1135. 'body.theme_light .border_bottom, body.theme_light #tbody tr:not(.invisible):last-of-type { border-bottom: solid 1px #666; }' +
  1136. 'body.theme_dark .border_left { border-left: solid 1px #111; }' +
  1137. 'body.theme_light .border_left { border-left: solid 1px #666; }' +
  1138. '.border_top_x { border-top: solid 1px #666; }' + // "x" = inverted for theme_dark
  1139. '.border_right_x { border-right: solid 1px #666; }' +
  1140. '.border_bottom_x { border-bottom: solid 1px #666; }' +
  1141. '.border_left_x { border-left: solid 1px #666; }' +
  1142. '.split_btn::after { border-left: solid 1px #333; }' +
  1143. // TEXT COLORS
  1144. 'body.theme_light a.text_color_111:hover, body.theme_light .text_color_111.selected, body.theme_light .selected .text_color_111 { color: #000; }' +
  1145. 'body.theme_dark a.text_color_111:hover, body.theme_dark .text_color_111.selected, body.theme_dark .selected .text_color_111 { color: #FFF; }' +
  1146. 'body.theme_light .text_color_111, body.editor_theme_light #content_text .text_color_111 { color: #111; }' +
  1147. 'body.theme_dark .text_color_111, body.editor_theme_dark #content_text .text_color_111 { color: #EEE; }' +
  1148. 'body.theme_light .text_color_333 { color: #333; }' +
  1149. 'body.theme_dark .text_color_333 { color: #CCC; }' +
  1150. 'body.theme_light .ignore, body.theme_light .ignore a { color: #555 !important; }' +
  1151. 'body.theme_dark .ignore, body.theme_dark .ignore a { color: #AAA !important; }'
  1152. ;
  1153. var $conditional_styles =
  1154. // PSEUDO-ELEMENTS
  1155. '#content_pane[data-content="has_image"] #title span::after' +
  1156. '{content: attr(data-after); }' +
  1157. '#content_pane[data-content="has_font"] #title::before { content: "Font:" }' +
  1158. '#content_pane[data-content="has_font_file"] #title::before { content: "Glyphs from font:" }' +
  1159. 'body.has_directory_source #title::before { content: "Source of: " }' +
  1160. '#content_pane[data-content="has_grid"] #title::before{ content: "Fonts and Images from:"; }' +
  1161. '#content_pane[data-content="has_grid"].has_font_grid #title::before { content: "Fonts from:"; }' +
  1162. '#content_pane[data-content="has_grid"].has_image_grid #title::before { content: "Images from:"; }' +
  1163. '#content_pane[data-content="has_grid"] #title::after { content: "'+ $current_dir +'"; }' +
  1164. '#content_pane[data-content="has_ignored"] #title::before{ content: "Ignored content:"; }' +
  1165. '#content_pane[data-content="has_dir"] #title::before {content: "Index of:"; }' +
  1166. 'body #sidebar_title div:before { content: "INDEX OF"; }' +
  1167. 'body.has_playlist #sidebar_title div:before { content: "PLAYLIST"; }' +
  1168. 'body.has_filelist #sidebar_title div:before { content: "FILELIST"; }' +
  1169. '#content_pane.has_audio #content_audio_title span::before, body #content_pane[data-content="has_video"] #title::before' +
  1170. '{content: "Playing: "; }' +
  1171. '#reload_btn.reset::before, #content_pane.has_zoom_image #reload_btn::before, #content_pane.has_scaled_image #reload_btn::before { content: "Reset"; }' + // reload button
  1172. '#content_pane[data-content="has_text_editor"] #close_btn::before { content: "Hide"; }' + // close button
  1173. '#content_pane[data-content="has_text_editor"] #title::after { content: "Text Editor"; }' +
  1174. '#content_pane[data-content="has_text_editor"].edited #title::after { content: "Text Editor (edited)"; }' +
  1175. 'body.edited #text_editor_row a:after, body.iframe_edited:not(.has_text_editor) #content_pane.has_iframe #title::after' +
  1176. '{content: " (edited)"; }' +
  1177. 'body.theme_light #theme span::before { content: "Light "; }' +
  1178. 'body.theme_dark #theme span::before { content: "Dark "; }' +
  1179. 'body.show_details #show::before { content: "Hide "; }' +
  1180. '#disable::after { content: "Enabled"; }' +
  1181. 'body.disable_text_editing #disable::after { content: "Disabled"; }' +
  1182. '#content_pane[data-content="has_text_editor"] #title::before { content: "" !important; }' +
  1183. // DISPLAY
  1184. 'body.has_hidden_sidebar #handle, body.has_stats #stats_summary, body[class$="list"] #stats_summary, body.has_stats #footer_links, body[class$="list"] #footer_links, body[class$="list"] #sidebar_buttons label, body.is_error #sidebar_header_body > div:not(:first-of-type), body.is_error #tfoot, body.is_non_local #show_invisibles_container, body.is_windows #show_invisibles_container' +
  1185. '{ display:none; }' +
  1186. 'body.ignore_ignored_files tr.ignore { display:none !important; }' +
  1187. 'body.has_media #play_toggle, body[class$="list"] #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, body[class$="list"] #close_btn' +
  1188. '{ display:unset; }' +
  1189. '#font_info:hover #font_info_body { display:table-row-group; }' +
  1190. 'body[class$="list"] #stats_summary_playlist_files, body.has_help #help_container' +
  1191. '{ display:table-row; }' +
  1192. 'body.has_images #grid_btn, body.has_fonts #grid_btn' +
  1193. '{ display:table-cell; }' +
  1194. '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' +
  1195. '{ display:block; }' +
  1196. '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' +
  1197. '{ display:inline-block; }' +
  1198. 'body.show_invisibles tr.invisible, body.has_stats tr.invisible, body.has_stats tr.ignore, body.has_stats tr.ignore.hovered, body.ignore_ignored_files.has_stats tr.ignore' +
  1199. '{ display:grid !important; }' +
  1200. '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, #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_video"] #content_container, body.has_stats #stats_summary_detailed_container, body.has_stats #stats_details_container, body.has_stats .summary_detailed, body[class$="list"] #stats_summary_playlist_container' +
  1201. '{ display:flex; }' +
  1202. '#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' +
  1203. '{display:none !important; }'+
  1204.  
  1205. 'body.theme_dark .invert, body.theme_dark #menu li > span::before, body.theme_dark #sorting span::before, body.theme_dark #sorting span::after' +
  1206. '{ filter:invert(1); }' +
  1207.  
  1208. '#content_pane[data-content="has_ignored"]::before { opacity:0.3; }' +
  1209. '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, #toggle_sidebar, #grid_btn, #dir_list.has_dir:not(:hover), #footer_links, .split_btn span, .disabled, body.focus_content #tbody tr:not(.hovered)' +
  1210. '{ opacity: 0.6; }' +
  1211. '#grid_btn:hover, #prev_next_btns span:hover, #toggle_sidebar:hover, #grid_btn:hover, #footer_links:hover, .split_btn span:hover' +
  1212. '{ opacity: 1.0; }' +
  1213. // HAS HIDDEN SIDEBAR
  1214. 'body.has_hidden_sidebar #toggle_sidebar { left:2px; transform:rotate(180deg); }' +
  1215. 'body.has_hidden_sidebar #sidebar_wrapper { width:0 !important; min-width:0; position:absolute; top:2px; left:-1px; }' +
  1216. 'body.has_hidden_sidebar #sidebar_header { z-index:unset; display:none; }' +
  1217. 'body.has_hidden_sidebar #sidebar { visibility:hidden; }' + // allows hidden sidebar to still be navigated by arrows
  1218. 'body.has_hidden_sidebar #dir_list { min-width:0; }' +
  1219. 'body.has_hidden_sidebar #content_pane { width:100% !important; }' +
  1220. 'body.has_hidden_sidebar #title_buttons_left { padding-left:24px; }' +
  1221. // HAS GRID
  1222. '#content_pane[data-content="has_grid"] #content_grid { display: grid; }' +
  1223. '#content_pane.has_hidden_grid #content_grid { max-height:100%; display:grid; overflow:hidden; }' +
  1224. // HAS ZOOM IMAGE
  1225. '#content_pane.has_scaled_image #content_image { display:grid; padding:0; }' +
  1226. '#content_pane.has_zoom_image #content_image img { width:unset; max-width:unset; max-height:unset; cursor: zoom-out; }' +
  1227. // FIGURE OUT HOW TO DELETE THESE:
  1228. '#content_pane[data-content="has_image"] #title_buttons_left, #content_pane[data-content="has_image"] #title_buttons_right, #content_pane[data-content="has_grid"] #title_buttons_left, #content_pane[data-content="has_grid"] #title_buttons_right, #content_pane[data-content^="has_font"] #title_buttons_left, #content_pane[data-content^="has_font"] #title_buttons_right, #content_pane[data-content^="has_glyph"] #title_buttons_right'+
  1229. '{ min-width: 9.5em; }'
  1230. ;
  1231. var $text_editor_styles =
  1232. 'html, body, #iframe_body { margin:0; padding:0; height:100%; position:relative; font-family:'+ $settings.UI_font +'; font-size: '+ $settings.UI_font_size +'; }' +
  1233. '#iframe_body { display:flex; flex-direction:column; position:relative; overflow:hidden; }' +
  1234. '#iframe_body #content_text { display:flex; height:100%; padding:0; position:absolute; z-index:1; width:100%; overflow:hidden; flex-direction:column; }' + // div
  1235. $warning_styles +
  1236. // TOOLBAR
  1237. '#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; }' + // table
  1238. '#toolbar td { padding:2px 0 0; }' +
  1239. '#toolbar_buttons { margin:0; padding:0; list-style:none; }' + // ul
  1240. '.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.6; color:#444; }' + // li
  1241. '#toggle_theme { background-image:'+ SVG_Text_Editing_UI_Icon('toggle_theme') +'; }' +
  1242. '#show_source { background-image:'+ SVG_Text_Editing_UI_Icon('show_markdown') +'; background-size:18px; }' +
  1243. '#show_preview { background-image:'+ SVG_Text_Editing_UI_Icon('show_preview') +'; }' +
  1244. '#show_html { background-image:'+ SVG_Text_Editing_UI_Icon('show_html') +'; background-size:20px; }' +
  1245. '#toggle_split { background-image:'+ SVG_Text_Editing_UI_Icon('toggle_split') +'; }' +
  1246. '#sync_scroll { padding:4px; float:left; opacity:1 !important; }' + // li
  1247. '#sync_scroll input { float:left; }' + //
  1248. '#sync_scroll label { width:8em; display:block; font-size:inherit; line-height:1.5; }' + //
  1249.  
  1250. '#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; }' +
  1251. '#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; }' +
  1252. '.edited #save_btn, .edited #save_btn ul { background-image:'+ SVG_Text_Editing_UI_Icon('save_btn_edited') +'; }' +
  1253. '#save_btn ul li { margin:0; padding:4px 6px; text-align:right; width:100%; white-space:pre; box-sizing:border-box; }' +
  1254. '#save_btn a { font-weight:normal; }' +
  1255. '#save_btn:hover ul { display:block; }' +
  1256. '#save_btn li:hover { background-color:#DDDDDD !important; }' +
  1257. '.editor_theme_dark #save_btn ul { border-color:#EEE; box-shadow:0px 4px 6px -3px #EEE;}' +
  1258. '.editor_theme_dark #save_btn li { border-color:#EEE; color:#111; background-color:#AEAEAE !important; }' +
  1259. '.editor_theme_dark #save_btn li:hover { background-color:#989898 !important; }' +
  1260. '.editor_theme_dark #save_text { border-color:#EEE; color:#111; }' +
  1261.  
  1262. '#clear_text { margin:0 4px; padding:4px; height:16px; float:right; cursor:pointer; }' + // li
  1263. '#toolbar li:hover, .preview_text:not(.split) #show_preview, body.source_text #show_source, .split_view #toggle_split, .edited #save_btn'+
  1264. '{ opacity:1; }' +
  1265. // TEXT CONTENT CONTAINERS
  1266. '#text_container { display:flex; flex-grow:1; overflow:hidden; }' + // li
  1267. '#text_source { margin:0; padding:1em; display:block; border:0; line-height:1.2; overflow-y:scroll; resize:none; font-family:monospace; box-sizing:border-box; z-index:1; font-size:'+ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') +'; }' + // textarea
  1268. '#text_preview, #html_preview { margin:0; padding:1em; overflow-y:scroll; box-sizing:border-box; z-index:1; }' + // div
  1269. '#html_preview { display:none; white-space:pre-wrap; word-break:break-word; font-family:monospace; }' +
  1270. 'body.has_html #html_preview { display:block; }' +
  1271. // RESIZE HANDLE
  1272. '#text_editing_handle { display:none; width:7px; position:absolute; top:0; bottom:0; left:calc(50% - 4px); cursor:col-resize; z-index:3; }' + //
  1273. '.split_view #text_editing_handle { display:block; }' +
  1274. // TEXT SOURCE
  1275. '#text_source, #text_preview, #html_preview { height:100%; }' +
  1276. '#text_source:focus, #text_preview:focus, #html_preview:focus { outline:none; }' +
  1277. '#text_source:focus { box-shadow: inset 0 0 4px #666; }' +
  1278. '.editor_theme_dark #text_source:focus { box-shadow: inset 0 0 6px #000; }' +
  1279. // TEXT PREVIEW
  1280. '#text_preview, #html_preview { padding: 14px; box-sizing:border-box; z-index:1; overflow-y:scroll; font-size:'+ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') +'; }' +
  1281. // dark theme
  1282. '.editor_theme_dark #toolbar { background: #404040; color:#EEEEEE; border-bottom: solid 1px #111; }' +
  1283. '.editor_theme_dark.split_view #text_preview, .editor_theme_dark.split_view #html_preview { border-left: solid 1px #111; }' +
  1284. '.editor_theme_dark #toolbar .has_background:not(#save_btn), .editor_theme_dark:not(.edited) #save_btn, .editor_theme_dark.edited #save_btn li' +
  1285. '{ filter: invert(1); }' +
  1286. 'body.theme_light:not(.editor_theme_dark) .background_color_DD_33, body.editor_theme_light .background_color_DD_33 { background-color: #DDDDDD; }' +
  1287. 'body.theme_dark:not(.editor_theme_light) .background_color_DD_33, body.editor_theme_dark .background_color_DD_33 { background-color: #333333; }' +
  1288. 'body.theme_light .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 { background-color: #EEEEEE; }' +
  1289. '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 { background-color: #222222; }' +
  1290. // SPLIT VIEWS
  1291. '.preview_text:not(.split_view) #text_source, .has_html:not(.split_view):not(.source_text) #text_source, .source_text:not(.split_view) #text_preview, .source_text:not(.split_view) #html_preview, body.has_html #text_preview, #html_preview, li#save_btn div, .comment'+
  1292. '{ display:none; }' +
  1293. '.preview_text:not(.split_view) #text_preview, .source_text:not(.split_view) #text_source, .source_text:not(.split_view) #html_preview, .has_html.preview_text:not(.split_view) #html_preview'+
  1294. '{ width:100% !important; }' +
  1295. '.split_view #text_preview, .split_view #html_preview { width:50%; border-left:solid 1px #999; }' +
  1296. '.split_view #text_source { width:50%; }' +
  1297. // custom previewed text styles
  1298. '#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*/,'') +'; }' +
  1299. '#text_preview th, #text_preview td { vertical-align:top; }' +
  1300. '#text_preview blockquote { margin-top:1em; margin-bottom:1em; color: #555; }' +
  1301. '#text_preview blockquote + blockquote { margin-top:0; }' +
  1302. '.markdown-body input[type="checkbox"] { margin-top:0.375em; margin-right:6px; float:left; }' +
  1303. 'h1 .uplink,h2 .uplink,h3 .uplink,h4 .uplink,h5 .uplink,h6 .uplink'+
  1304. '{ display:inline-block; font-size:0.875em; transition: opacity 0.25s; opacity:0; cursor:pointer; margin:0; padding:0; }' +
  1305. 'h1:hover .uplink,h2:hover .uplink,h3:hover .uplink,h4:hover .uplink,h5:hover .uplink,h6:hover .uplink'+
  1306. '{ transition: opacity 0.25s; opacity:0.5; }' +
  1307. '#text_preview table { font-size:inherit; }' +
  1308. '#text_preview table th { background-color:#EEE; }' +
  1309. '.markdown-body::before, .markdown-body::after { display:none !important; background:transparent; }' +
  1310. // disabled text editing
  1311. '#iframe_body.text_editing_disabled #toolbar, #iframe_body.text_editing_disabled #preview_text, #iframe_body.text_editing_disabled #text_editing_handle'+
  1312. '{ display:none; }' +
  1313. '#iframe_body.text_editing_disabled #text_source.disabled { top:0; background:white; }' +
  1314. '#iframe_body.editor_theme_light .background_color_D0_50 { background-color: #D0D0D0; }' +
  1315. '#iframe_body.editor_theme_dark .background_color_D0_50 { background-color: #505050; }' +
  1316. '#iframe_body.editor_theme_light .background_color_E0_60 { background-color: #E0E0E0; }' +
  1317. '#iframe_body.editor_theme_dark .background_color_E0_60 { background-color: #606060; }' +
  1318. '#iframe_body.editor_theme_light .text_color_111, #iframe_body.editor_theme_light #content_text .text_color_111 { color: #111; }' +
  1319. '#iframe_body.editor_theme_dark .text_color_111, #iframe_body.editor_theme_dark #content_text .text_color_111 { color: #EEE; }' +
  1320. '#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; }'
  1321. ;
  1322. var $iframe_dir_styles =
  1323. ':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; }' +
  1324. '#iframe_body { width:100%; display:flex; flex-direction:column; font-size:'+ (parseFloat($settings.UI_font_size) * 0.875) + $settings.UI_font_size.replace(/\d*/,'') +'; }' + //
  1325. '#iframe_body.theme_dark { background-color:#333; }' +
  1326. 'a, a:hover { text-decoration: none !important; }' +
  1327. // iframe header
  1328. '#thead { flex-direction:row; font-size: 0.875rem; user-select:none; -webkit-user-select:none; }' +
  1329. '#change_dirs { display:flex; flex-direction:row; justify-content:space-between; white-space:pre; }' +
  1330. '#change_dirs a { padding-right:0 !important; padding-left:0 !important;}' +
  1331. '#change_dirs span:hover { font-weight:bold; }' +
  1332. '#parent { padding:5px 3px 5px 0; text-align:left; flex-grow:1; }' +
  1333. '#open_in_sidebar { padding:5px 0 5px 3px; text-align:right; flex-grow:1; }' +
  1334. '#parent a::before, #open_in_sidebar a::after { content:""; width:16px; height:10px; display:inline-block; }' +
  1335. '#open_in_sidebar a::after { background-image:'+ SVG_UI_Icon('chevron_left') +'; background-repeat: no-repeat; background-size:6px; background-position: center; }' +
  1336. '.theme_dark #parent a::before, .theme_dark #open_in_sidebar a::after { filter:invert(1); }' +
  1337. // sorting
  1338. '#sorting_row_1 div, #sorting_row_2 div { flex-grow:1; padding:0; }' +
  1339. '#sorting_row_1 { display:flex; cursor:pointer; justify-content:space-between; flex-direction:row; }' +
  1340. '#sorting_row_1 div { width:50%; }' +
  1341. '.sorting span { display:inline-block; padding:3px 0; }' +
  1342. '.sorting:hover span { font-weight:bold; }' + //
  1343. '.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; }' +
  1344. '.sorting.down span::after { transform:rotate(180deg) }' +
  1345. '.theme_dark .sorting span::before, .theme_dark .sorting span::after { filter:invert(1); }' +
  1346. '#sort_by_name, #sort_by_ext { text-align:left; }' +
  1347. '#sort_by_size, #sort_by_date, #sort_by_default, #sort_by_kind { text-align:right; }' +
  1348. // iframe dir list
  1349. '#iframe_dir_list_wrapper { width:100%; height:100%; display:block; position:relative; overflow:scroll; }' + //
  1350. '#dir_list { width:100%; height:100%; line-height:1.5; position:relative; overflow:hidden; border-collapse:collapse; }' + //
  1351. '#tbody { font-size:0.875rem; counter-reset:row; height:100%; overflow:hidden; }' + //
  1352. '#tbody tr, #sorting_row_2 { display: grid; grid-gap:0; grid-template-columns: minmax(24em,100%) minmax(5.5em,8em) minmax(6.5em,14em) minmax(5.5em,5.5em); }' +
  1353. '.tbody_row_cell_details { white-space:pre; font-variant-numeric:tabular-nums; padding:3px 0; }' +
  1354. '.tbody_row_cell_name_a { padding:3px 0; }' +
  1355. '.tbody_row_cell_name_a::before { display:none; counter-increment:row; content:counter(row); width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; margin-top:2px; padding:0; float:left; text-align:right; font-variant-numeric:tabular-nums; }' +
  1356. '.show_numbers #tbody td.name a::before { display:inline-block; }' +
  1357. '.tbody_row_cell_name_a_span { display:flex; padding:2px 0; word-break:break-word; }' +
  1358. '.tbody_row_cell_name_a_span input { margin:2px 6px 0 0; }' +
  1359. '.tbody_row_cell_details.name { grid-column: 1; text-align:left; }' +
  1360. '.tbody_row_cell_details.size { grid-column: 2; padding-right:16px; padding-left:0; text-align:right; }' +
  1361. '.tbody_row_cell_details.date { grid-column: 3; padding-right:16px; padding-left:0; white-space:normal; overflow:hidden; max-height:1em; overflow-wrap:break-word; text-align:right; }' +
  1362. '.tbody_row_cell_details.date span { white-space:pre; display:inline-block; }' +
  1363. '.tbody_row_cell_details.kind { grid-column: 4; text-align:right; padding-right:16px; }' +
  1364. '.tbody_row_cell_details.ext { display:none; }' +
  1365. 'tr:hover .tbody_row_cell_name_a, tr.selected .tbody_row_cell_name_a, tr.playing .tbody_row_cell_name_a { font-weight:bold; }' +
  1366. 'tr.ignore, tr.ignore a { cursor:not-allowed; }' +
  1367. 'tr.invisible, #iframe_body.ignore_ignored_files tr.ignore { display:none !important; }' + // display
  1368. '#iframe_body.show_invisibles tr.invisible { display:grid; }' + // display
  1369. // '#iframe_body tr.dimmed { background-color: rgba(172,202,235,0.50) !important; }' +
  1370. CSS_UI_Icon_Rules() + // background icons
  1371. '.has_icon_before::before { content:""; display:block; float:left; 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; }' +
  1372. '#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'+
  1373. '{ content:""; height:9px; background-image:'+ SVG_UI_Icon('check_mark') +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  1374. '#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'+
  1375. '{ background-image:'+ SVG_UI_Icon('chevron_up') +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  1376. // GECKO STYLES:
  1377. 'body.is_gecko #dir_list tr td { min-width:calc(100% - 24px); }' +
  1378. 'body.is_gecko #dir_list tr td.name span { display:-webkit-box; width:calc(100% - 64px); }' +
  1379. 'body.is_gecko .dir::before { content:"" !important; display:none !important; }'
  1380. ;
  1381. // Gecko Styles:
  1382. const $gecko_style_rules =
  1383. 'html, body.is_gecko { border: solid 1px gray !important; }' +
  1384. '.dir::before { content:"" !important; display:none !important; }' +
  1385. 'body.is_gecko button { padding:revert; }' +
  1386. 'body.is_gecko #grid_btn .menu { top:-7px; left:-120px; }' +
  1387. 'body.is_gecko thead { font-size:100%; }' +
  1388. 'body.is_gecko #dir_list .dir::before { position:absolute; }' +
  1389. 'body.is_gecko #dir_list tr td.name span { display:-webkit-box; width:calc(100% - 56px); }' +
  1390. 'body.is_gecko.use_default_icons:not(.is_converted_list) #dir_list .file .name .icon { padding-left:4px; background:none; }' +
  1391. 'body.is_gecko.use_default_icons #dir_list .file .name .icon img { margin-right:6px; height:14px; }' +
  1392. 'body.is_gecko #tbody > tr > td:not(:first-of-type){ float:left }' +
  1393. 'body.is_gecko #content_audio_title span { padding-top: 6px;, padding-bottom: 0; }' +
  1394. 'body.is_gecko #audio,body.is_gecko #audio_container { background-color: transparent; }' +
  1395. 'body.is_gecko.theme_dark #prev_track, body.is_gecko.theme_dark #next_track, body.is_gecko.theme_dark #close_audio { filter: invert(1); }' +
  1396. 'body.is_gecko #content_pane.has_zoom_image #content_image { display:block !important;}'
  1397. ;
  1398. const $safari_style_rules =
  1399. 'body.is_safari button { background-color: #FFF; }' +
  1400. 'body.is_safari.theme_dark #prev_track, body.is_safari.theme_dark #next_track, body.is_safari.theme_dark #close_audio { filter: invert(1); }'
  1401. ;
  1402. // ADD STYLES
  1403. const $font_styles = document.createElement('style');
  1404. const $font_grid_styles = document.createElement('style');
  1405. function addStyles(el) {
  1406. el.find('style, link[rel="stylesheet"], link[href$="css"]').remove(); // remove any existing stylesheets
  1407. el.append('<style id="conditional_styles">'+ $conditional_styles +'</style><style id="color_and_background_styles">'+ $color_and_background_styles +'</style><style id="main_styles">'+ $main_styles +'</style>');
  1408. el.append($font_styles); // empty until font is previewed
  1409. el.append($font_grid_styles); // empty until font grid is made
  1410. if ( getBrowser() === 'firefox' ) { el.append('<style id="gecko_style_rules">'+ $gecko_style_rules +'</style>'); }
  1411. if ( getBrowser() === 'safari' ) { el.append('<style id="safari_style_rules">'+ $safari_style_rules +'</style>'); }
  1412. }
  1413. // ***** END STYLES ***** //
  1414.  
  1415. // ***** INDEX PREP ***** //
  1416. // 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.
  1417. function getIndexType() {
  1418. let parentLinkParent; // Possible elements: pre, li, td, th, div -- used to determine index type
  1419. // Try to find parent directory link:
  1420. let parentLink = $('a:contains("Parent Directory"), a:contains("parent directory"), a[href="../"], a[href="/"], a[href^="?"], img[alt*="PARENTDIR"]');
  1421. if ( parentLink.length === 0 ) {
  1422. parentLinkParent = document.querySelectorAll('body > ul li, body > pre, body > table:last-of-type tr td');
  1423. } else {
  1424. parentLinkParent = parentLink.parent(); // use original found parentLink
  1425. }
  1426. // If no parentLinkParent found, type = error; else return parent node name
  1427. let nodeName = ( parentLinkParent[0] !== undefined ? parentLinkParent[0].nodeName.toLowerCase() : '');
  1428. switch(true) {
  1429. case parentLinkParent.length === 0:
  1430. nodeName = 'error';
  1431. break;
  1432. case $protocol.startsWith('file'):
  1433. if ( getBrowser() === 'firefox' ) { nodeName = 'gecko'; }
  1434. break;
  1435. }
  1436. let types = {'gecko':'gecko','li':'list','pre':'pre','th':'table','td':'table','div':'default','error':'error'};
  1437. let type = types[nodeName];
  1438. return type;
  1439. }
  1440.  
  1441. // Return Index items, Index type, remove parent directory link, and add body class.
  1442. function getIndexItems() {
  1443. const type = getIndexType();
  1444. let items;
  1445. switch(type) {
  1446. case 'gecko': items = $('body').find('> table > tbody'); break;
  1447. case 'list': items = $('body').find('> ul'); break;
  1448. case 'pre': items = $('body').find('> pre').html(); break;
  1449. case 'table':
  1450. case 'td':
  1451. if ( $('table > tbody').length === 1 ) {
  1452. items = $('body').find('table > tbody');
  1453. } else {
  1454. items = $('body').find('table'); // tables without tbody
  1455. }
  1456. // if ( $('table.PARENTTABLE > tbody').length === 1 ) {
  1457. // items = $('body').find('table.PARENTTABLE > tbody');
  1458. // } else {
  1459. // items = $('body').find('table.PARENTTABLE'); // tables without tbody
  1460. // }
  1461. items.find('tr th, tr td:contains(Parent Directory), a[href="../"], hr').parent('tr').remove();
  1462. break;
  1463. case 'default': items = $('body').find('> table').find('> tbody'); break;
  1464. case 'error': items = $('body').html(); break;
  1465. }
  1466. return [items,type];
  1467. }
  1468.  
  1469. // Index Prep: convert rows and return array of rows, with link, size, date-modified
  1470. function convertIndexItems(type,items) {
  1471. let converted = [];
  1472. switch(type) {
  1473. case 'gecko': converted = convertGeckoType(items); break;
  1474. case 'list': converted = convertListType(items); break;
  1475. case 'pre': converted = convertPreType(items); break;
  1476. case 'table': case 'default': converted = convertTableType(type,items); break;
  1477. case 'error': converted = ''; break;
  1478. }
  1479. return converted;
  1480. }
  1481. // Index Prep: convert list type function
  1482. function convertGeckoType(items) {
  1483. let preppedIndex = [];
  1484. const rows = Array.from(items.find('> tr'));
  1485. for ( let row of rows ) {
  1486. let preppedRow = [], cellContents = '', cells = Array.from( $(row).find('> td') ), link = ($(cells).find('a').attr('href'));
  1487. for ( let cell of cells ) {
  1488. cellContents = cell.innerText;
  1489. cellContents = ( cellContents !== undefined ? cellContents.trim() : '');
  1490. preppedRow.push(cellContents);
  1491. }
  1492. preppedRow[1] = preppedRow[1].replace(/\s*KB/,'000'); // convert reported size in KB to total bytes
  1493. preppedRow[2] = preppedRow[2] + ' '+ preppedRow[3];
  1494. preppedRow = preppedRow.slice(1,-1);
  1495. if ( link.length > 0 && link !== '/' && link !== '../' ) { preppedRow.unshift(link); }
  1496. if ( preppedRow.length > 0 ) { preppedIndex.push(preppedRow); }
  1497. }
  1498. return preppedIndex;
  1499. }
  1500. // Index Prep: convert list type function
  1501. function convertListType(items) {
  1502. let preppedIndex = [];
  1503. const rows = items[0].children;
  1504. for ( let i = rows.length; i--; ) {
  1505. let row = rows[i];
  1506. if ( row.innerHTML.indexOf('Parent Directory') === -1 ) {
  1507. let preppedRow = [];
  1508. let link = $(row).find('a').attr('href');
  1509. row = row.innerHTML.replace(/<a .+?<\/a>\s*/,'');
  1510. let cells = row.split(' ');
  1511. for ( let cell of cells ) {
  1512. if ( cell.trim().length > 0 ) { preppedRow.push(cell); }
  1513. }
  1514. if ( link.length > 0 && link !== '/' && link !== '../' ) { preppedRow.unshift(link); }
  1515. if ( preppedRow.length > 0 ) { preppedIndex.push(preppedRow); }
  1516. }
  1517. }
  1518. return preppedIndex;
  1519. }
  1520. // Index Prep: convert pre type function
  1521. function convertPreType(items) {
  1522. let preppedIndex = [];
  1523. const spaces = /\s{2,}/;
  1524. items = items.replace(/\[\s*?\]/g,'[]') // remove empty 'alt=[ ]'
  1525. .replace(/\s*<(hr|img[^>])>\s*/g,'') // remove hr and images
  1526. .replace(/\s*<a href="[^>]+?>\s*Parent Directory\s*<\/a>\s*/g,'') // remove parent links
  1527. .replace(/\s*<a href="\?[^>]+?>[^<]+?<\/a>\s*/g,'') // remove sorting links (href beginning with "?")
  1528. .replace(/(<a [^>]+?>)[^<]+?<\/a>/g,' $1</a> ') // remove link display text and add two spaces after
  1529. .replace(/(\%20){2,}/g,'\%20'); // remove extra spaces in hrefs
  1530. const rows = items.split('\n'); // create array of rows from items
  1531. for ( let i = rows.length; i--; ) {
  1532. let row = rows[i];
  1533. let preppedRow = [];
  1534. let cells = row.split(spaces); // assumes pre-type only uses two or more spaces between "columns"
  1535. let link;
  1536. for ( let j = cells.length; j--; ) {
  1537. let cell = cells[j];
  1538. if ( cell.trim().length > 0 ) {
  1539. if ( !cell.startsWith('<a ') ) { preppedRow.push(cell); } else { link = cell.split('"')[1]; } // extract link
  1540. }
  1541. }
  1542. if ( link !== undefined ) { preppedRow.unshift(link); } // add link to front of preppedRow
  1543. if ( preppedRow.length > 1 ) { preppedIndex.push(preppedRow); } // add prepped row to index
  1544. }
  1545. return preppedIndex;
  1546. }
  1547. // Index Prep: convert table type function
  1548. function convertTableType(type,items) { // for local chrome indexes and server-generated table-type indexes
  1549. let preppedIndex = [];
  1550. const rows = items[0].children;
  1551. for ( let i = rows.length; i--; ) {
  1552. let row = rows[i], preppedRow = [], link, cell, cells = row.cells;
  1553. for ( let j = cells.length; j--; ) {
  1554. switch(true) {
  1555. case cells[j].children[0] !== undefined && cells[j].children[0].nodeName === 'A':
  1556. link = cells[j].children[0].attributes.href.value;
  1557. break;
  1558. case cells[j].innerHTML !== '&nbsp;' && cells[j].innerHTML.indexOf('<img') === -1:
  1559. cell = cells[j].innerHTML.trim().replace(/^-$/m,'—');
  1560. if ( cell.length > 0 ) { preppedRow.push( cell ); }
  1561. break;
  1562. }
  1563. }
  1564. if ( link !== undefined ) { preppedRow.unshift(link); }
  1565. if ( preppedRow.length > 1 ) { preppedIndex.push(preppedRow); } // preppedRow.length > 2 in order to omit parent directory row
  1566. }
  1567. return preppedIndex;
  1568. }
  1569. // Index Prep: Build new Index from prepped rows
  1570. function buildNewIndex(id,preppedIndex,sort,type) {
  1571. let newIndexItems = [], bodyClasses = new Set(), rowLinkPrefix = '';
  1572. if (type === 'gecko' ) { rowLinkPrefix = document.baseURI; } // because FF only uses the
  1573. let stats = { total:0, dir:0, file:0, hidden:0, invisible:0, ignore:0, dir_ignore:0, dir_invisible:0, file_invisible:0, file_ignore:0 };
  1574. let selected = ( getQuery('selected').length > 0 ? Number(getQuery('selected')) : null);
  1575. for ( let i = preppedIndex.length; i--; ) {
  1576. let row = preppedIndex[i];
  1577. // Get row attrs and text
  1578. const rowLink = row[0] !== undefined ? row[0].replace(/&amp;/g,'&') : '';// .replace(//,''); // decode some reserved characters
  1579. let url = new URL(rowLink.replace(/^\//,''),document.baseURI);
  1580. const rowName = getItemName(rowLink).replace(/^\s/m,'\&nbsp;').replace(/^\//m,'').replace(/([-_——])/g,'$1<wbr>'); // display name, with word breaks added after unbreakable chars
  1581. const rowSortName = getItemName(rowLink).replace(/^\//m,'').replace('/','').toLocaleLowerCase();
  1582. const sizesAndDates = getItemSizeAndDate(row);
  1583. const rowSize = sizesAndDates[0];
  1584. const rowSortSize = sizesAndDates[1];
  1585. const rowDate = sizesAndDates[2];
  1586. const rowSortDate = sizesAndDates[3];
  1587. const rowExt = getItemExt(rowLink);
  1588. const rowSortKind = getItemKind(rowExt);
  1589. const rowKind = rowSortKind.slice(0,1).toUpperCase() + rowSortKind.slice(1);
  1590. const rowClasses = getItemClasses(rowName,rowSortKind,rowExt);
  1591. // Assemble row elements
  1592. let newRow = $('<tr></tr>').attr('id','rowid-'+ i).addClass(rowClasses).attr('data-kind',rowSortKind).attr('data-ext',rowExt);
  1593. newRow.data('data-url',url);
  1594. if ( window.self === window.top && selected !== undefined && i === selected ) { newRow.addClass('selected'); } // autoSelect file
  1595. const checkbox = ( /media/.test(rowClasses) ? $('<input type="checkbox" tabindex="-1" checked />') : '' );
  1596. const nameSpan = $('<span class="tbody_row_cell_name_a_span has_icon_before"></span>').append(checkbox, rowName);
  1597. const cellLink = $('<a></a>').addClass('icon tbody_row_cell_name_a text_color_111').attr('href',rowLinkPrefix + rowLink).append(nameSpan);
  1598. const cellName = $('<td></td>').addClass('tbody_row_cell tbody_row_cell_name name').attr('data-name',rowSortName).append(cellLink);
  1599. const cellSize = $('<td></td>').addClass('tbody_row_cell_details size details').attr('data-size',rowSortSize).append(rowSize);
  1600. const cellDate = $('<td></td>').addClass('tbody_row_cell_details date details').attr('data-date',rowSortDate).append(rowDate);
  1601. const cellKind = $('<td></td>').addClass('tbody_row_cell_details kind details').attr('data-kind',rowSortKind).append(rowKind);
  1602. const cellExt = $('<td></td>').attr('data-ext',rowExt).addClass('tbody_row_cell_details ext details');
  1603. // Assemble row
  1604. newRow.append(cellName, cellSize, cellDate, cellKind, cellExt);
  1605. newIndexItems.push(newRow[0]);
  1606. // body classes (set object: unique values)
  1607. switch(rowSortKind) {
  1608. case 'audio': bodyClasses.add('has_media').add('has_audio'); break;
  1609. case 'video': bodyClasses.add('has_media').add('has_video'); break;
  1610. case 'font' : bodyClasses.add('has_fonts'); break;
  1611. case 'image': bodyClasses.add('has_images'); break;
  1612. }
  1613. switch(type) {
  1614. case 'gecko': bodyClasses.add('is_converted_gecko'); break;
  1615. case 'list': bodyClasses.add('is_converted_list'); break;
  1616. case 'pre': bodyClasses.add('is_converted_pre'); break;
  1617. case 'table':
  1618. case 'td': bodyClasses.add('is_converted_table'); break;
  1619. case 'default': bodyClasses.add('is_default'); break;
  1620. case 'error': bodyClasses.add('is_error'); break;
  1621. }
  1622. // Stats for visible files
  1623. stats.total = ++stats.total; // increment total number of items
  1624. if ( rowSortKind === 'dir' ) { ++stats.dir; } else { ++stats.file; } // increment total number of dirs or files
  1625. if ( !stats[rowKind] && !/invisible|ignore/.test(rowClasses) ) { stats[rowKind] = 1; } else { ++stats[rowKind]; } // increment rowKind or add it to the stats if it is not already there
  1626. // stats for invisible/ignored files
  1627. if ( /invisible|ignore/.test(rowClasses) ) { // if it's invisible or ignored --> why is this so complicated?
  1628. ++stats.hidden;
  1629. if ( /invisible/.test(rowClasses) ) { // if it's invisible...
  1630. ++stats.invisible; // increment the invisible count
  1631. if ( !stats[rowKind +'_invisible'] ) { stats[rowKind +'_invisible'] = 1; } else { ++stats[rowKind +'_invisible']; } // increment rowKind or add it
  1632. if ( /dir/.test(rowClasses) ) { // if it's an invisible directory...
  1633. if ( !stats.dir_invisible ) { stats.dir_invisible = 1; } else { ++stats.dir_invisible; } // increment count or add it to the list
  1634. } else { // or an invisible file
  1635. if ( !stats.file_invisible ) { stats.file_invisible = 1; } else { ++stats.file_invisible; } // increment count or add it to the list
  1636. }
  1637. } else { // else it is ignored
  1638. ++stats.ignore; // increment the ignored count
  1639. if ( !stats[rowKind +'_ignore'] ) { stats[rowKind +'_ignore'] = 1; } else { ++stats[rowKind +'_ignore']; }
  1640. if ( /dir/.test(rowClasses) ) { // if it's an ignored directory... (is there such a thing?)
  1641. if ( !stats.dir_ignore ) { stats.dir_ignore = 1; } else { ++stats.dir_ignore; }
  1642. } else { // on an ignored file...
  1643. if ( !stats.file_ignore ) { stats.file_ignore = 1; } else { ++stats.file_ignore; }
  1644. }
  1645. }
  1646. }
  1647. }
  1648. $('body').addClass(Array.from(bodyClasses).join(' ')).data('stats',stats); // add stats data to body and body classes
  1649. if ( sort === undefined ) { sort = getQuery('sort_by'); } // get sorting pref
  1650. let sort_direction = ( getQuery('sort_direction') === 'up' ? -1 : 1 ); // get sort direction
  1651. let sortedIndexItems = sortDirList($(newIndexItems), 'sort_by_'+ sort, sort_direction); // make initial sort
  1652. return sortedIndexItems;
  1653. }
  1654. // Index Prep: get row name
  1655. function getItemName(link) {
  1656. let name;
  1657. try { name = decodeURIComponentSafe(link); }
  1658. catch (error) { name = link.replace(/%20/g,' ').replace(/\s{2,}/,' '); }
  1659.  
  1660. if ( name.split('/').length > 2 ) { // get name only if x = full path
  1661. let arr = name.split('/');
  1662. if ( name.startsWith('/') && name.endsWith('/') ) { // dirs
  1663. name = arr[arr.length - 2] + '/';
  1664. } else {
  1665. name = arr[arr.length - 1]; // files
  1666. }
  1667. }
  1668. return name;
  1669. }
  1670. // Index Prep: get row classes
  1671. function getItemClasses(name,kind,ext) {
  1672. if ( ext === undefined ) { ext = ''; }
  1673. let itemClasses = [];
  1674. itemClasses.push(kind);
  1675. if ( !/dir|app/.test(ext) ) { itemClasses.push('file'); }
  1676. if ( ext === 'app' ) { if ( $settings.apps_as_dirs === false ) { itemClasses.push('file'); } else { itemClasses.push('dir'); } }
  1677. if ( name.endsWith('symlink') || name.endsWith('alias') || name.endsWith('symbolic link') ) { itemClasses.push('alias'); }
  1678. if ( name.startsWith('.') ) { itemClasses.push('invisible'); }
  1679. if ( !JSON.stringify($row_types).match( escapeStr(ext) ) ) { // else classify as "other" if extension is not in $row_types.
  1680. itemClasses.push('other'); //itemType = 'Other'; itemKind = 'other';
  1681. } else if ( kind === '' ) {
  1682. // itemClasses.push('code'); //itemType = 'Code'; itemKind = 'code';
  1683. } else {
  1684. itemClasses.push(ext);
  1685. }
  1686. if ( $row_settings.ignore.includes( ext ) ) { itemClasses.push('ignore'); }
  1687. if ( $row_settings.exclude.includes( ext ) ) { itemClasses.push('exclude'); }
  1688. if ( kind === 'audio' || kind === 'video' ) { itemClasses.push('media'); }
  1689. itemClasses = Array.from(new Set(itemClasses)); // remove dupe classes
  1690. return itemClasses.join(' ');
  1691. }
  1692. // Index Prep: get formatted row size and date
  1693. function getItemSizeAndDate(cells) {
  1694. let sizesAndDates = [], rowDisplaySize, rowSortSize, rowDisplayDate, rowSortDate;
  1695. if ( cells.length > 1 ) {
  1696. if ( cells[1].search(/[-:\/]/) !== -1 ) { // test for typical date/time separators.
  1697. rowDisplayDate = cells[1]; rowDisplaySize = cells[2];
  1698. } else {
  1699. rowDisplayDate = cells[2]; rowDisplaySize = cells[1];
  1700. }
  1701. }
  1702. // size
  1703. let sizeUnits = /[BYTES|B|K|KB|MB|GB|TB|PB|EB|ZB|YB]/;
  1704. if ( /undefined|—|-|,/.test(rowDisplaySize) || rowDisplaySize === '' ) { // if size is undefined, empty, or punctuation
  1705. rowDisplaySize = '&mdash;'; rowSortSize = '0'; // if no size supplied, use these defaults
  1706. } else {
  1707. rowSortSize = getItemSortSize(rowDisplaySize);
  1708. if ( !rowDisplaySize.toUpperCase().match(sizeUnits) ) { // if provided size is only numeric
  1709. rowDisplaySize = formatBytes(rowDisplaySize,1); // format byte size
  1710. } else {
  1711. rowDisplaySize = rowDisplaySize.replace('K','k').replace(/(\d+)\s*([A-z])/,'$1 $2'); // ensure display size has space between number and units
  1712. }
  1713. }
  1714. // date
  1715. if ( [undefined,'','-'].includes(rowDisplayDate) ) { rowDisplayDate = '&mdash;'; rowSortDate = '0'; } else { rowSortDate = getItemDate(rowDisplayDate); }
  1716. rowDisplayDate = rowDisplayDate.replace(/, (.+)/,'<span>, $1</span>'); // add span for short date
  1717. sizesAndDates.push(rowDisplaySize,rowSortSize,rowDisplayDate,rowSortDate);
  1718. return sizesAndDates;
  1719. }
  1720. // Index Prep: get row size for sorting
  1721. function getItemSortSize(val) {
  1722. let sortSize, values = val.replace(/(\d+)\s*([A-z]+)/,'$1 $2').split(' '), size = values[0], unit = values[1];
  1723. if ( unit !== undefined ) { unit = unit.toUpperCase(); }
  1724. const factor = { '':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
  1725. sortSize = size * factor[unit]; // convert byte size to multiplication factor
  1726. return sortSize;
  1727. }
  1728. // convert numeric sizes to display format
  1729. function formatBytes(val, decimals) {
  1730. if (val === 0) return '0 Bytes';
  1731. 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));
  1732. return parseFloat((val / Math.pow(k, i)).toFixed(dm)) +' '+ sizes[i];
  1733. }
  1734. // process date
  1735. function processDate(match,p1,p2,p3) { //date formats: 2017-10-09 13:12 || 2015-07-25T02:02:57.000Z || 12-Mon-2017 21:11
  1736. const mo = 'JanFebMarAprMayJunJulAugSepOctNovDec'.indexOf(p2)/3 + 1; // e.g., convert month into number, or use number
  1737. return p3 +'-'+ mo +'-'+ p1;
  1738. }
  1739. // Index Prep: get row date 2015-07-25T02:22:00.000Z
  1740. function getItemDate(val) {
  1741. let sortDate = val.replace(/^(\d{2})-(\w{3})-(\d{4})/m, processDate) // convert Month to number
  1742. .replace(/\b(\d{1})[-:/]/g,'0$1/') // add leading 0 for single digit numbers
  1743. .replace(/(\d{2})\/(\d{2})\/(\d{2}),/,'$3$1$2') // reorder MM/DD/YY dates
  1744. .replace(/-|:|\s+|\//g,''); // remove spacing characters
  1745. return sortDate;
  1746. }
  1747. // Index Prep: get row kind
  1748. function getItemKind(ext) {
  1749. let kind = '';
  1750. switch(true) {
  1751. case ext === 'dir' || ext === 'app':
  1752. kind = ext;
  1753. break;
  1754. default:
  1755. for ( let types in $row_types ) {
  1756. if ( $row_types[types].includes( ext ) ) {
  1757. kind = types;
  1758. return kind;
  1759. } else {
  1760. kind = 'other';
  1761. }
  1762. }
  1763. }
  1764. return kind;
  1765. }
  1766. // Index Prep: get row extension
  1767. function getItemExt(link) {
  1768. let ext = '';
  1769. switch(true) {
  1770. case link.endsWith('app/') || link.endsWith('exe'):
  1771. ext = 'app';
  1772. break;
  1773. case link.endsWith('/'):
  1774. ext = 'dir';
  1775. break;
  1776. case !/\./.test(link): // if no '.' in link (typical for bin files), ...
  1777. break;
  1778. default: // find the last . and get the remaining characters
  1779. ext = link.slice(link.lastIndexOf('.') + 1).toLowerCase();
  1780. }
  1781. return ext;
  1782. }
  1783. // END INDEX PREP
  1784. // ***** MAKE NEW INDEX ***** //
  1785. function makeNewIndex(el,sort) {
  1786. const indexItems = getIndexItems(el), items = indexItems[0], type = indexItems[1];
  1787. const convertedIndex = convertIndexItems( type, items ); // = array of rows: ["link","date","size"]
  1788. if ( type === 'error' ) {
  1789. return [convertedIndex];
  1790. } else {
  1791. let newIndex = buildNewIndex( $(el).attr('id'), convertedIndex, sort, type );
  1792. return [newIndex];
  1793. }
  1794. }
  1795. // get and set index stats
  1796. function sortObject(obj) { return Object.keys(obj).sort().reduce((r, k) => (r[k] = obj[k], r), {}); }
  1797.  
  1798. function getIndexStats() {
  1799. if ( $('body').data('stats') === undefined ) { return; }
  1800. var stats = $('body').data('stats'), i = 1, padding_style = '';
  1801. stats = sortObject(stats); // sort by kind
  1802. const totalHidden = '('+ stats.hidden +' invisible or ignored)';
  1803. const hiddenDirs = ' ('+ (stats.dir_invisible + stats.dir_ignore) +' invisible or ignored)';
  1804. const hiddenFiles = ' ('+ (stats.file_invisible + stats.file_ignore) +' invisible or ignored)';
  1805. const dirsLabel = ( stats.dir === 1 ? ' Directory' : ' Directories' );
  1806. const filesLabel = ( stats.file === 1 ? ' File' : ' Files' );
  1807. let details_rows = '';
  1808. var displayName = '', hiddenName = '';
  1809. for ( let stat in stats ) {
  1810. if ( i !== 1 ) { padding_style = 'first'; }
  1811. if ( Number.isNaN(stats[stat]) ) { stats[stat] = 0; }
  1812. displayName = stat.replace(/_(.+)/,' ($1)');
  1813. if ( !/total|dir|file|^hidden|^invisible|^ignore/m.test(stat) ) { // don't include these stats in details rows
  1814. details_rows += (`<div class="${stat.toLowerCase()}"><span class="stats_count ${padding_style}">${stats[stat]} </span><span class="stats_kind ${stat.replace('_',' ').toLowerCase()} ${padding_style}"><a class="icon"><span class="has_icon_before">${displayName}${hiddenName}</span></a></span></div>`); // create list of kinds and counts
  1815. }
  1816. i = 0;
  1817. }
  1818. $('#stats_summary').html(stats.total +' Items: '+ stats.dir + dirsLabel +', '+ stats.file + filesLabel);
  1819. $('#stats_summary_detailed_total').html( '<span class="stats_count ">'+ stats.total +' Items '+ totalHidden +'</span>' );
  1820. $('#stats_summary_detailed_dirs').html( '<span class="stats_count">'+ stats.dir +'</span><span class="stats_kind dir has_icon_before">'+ dirsLabel + hiddenDirs +'</span>' );
  1821. $('#stats_summary_detailed_files').html( '<span class="stats_count">'+ stats.file +'</span><span class="stats_kind file has_icon_before">'+ filesLabel + hiddenFiles +'</span>' );
  1822. $('#total').html( stats.total +' Items' );
  1823. $('#total_hidden').html( totalHidden );
  1824. $('#dirs_length').html( stats.dir );
  1825. $('#dirs_label_hidden_dirs').html( dirsLabel + hiddenDirs );
  1826. $('#files_length').html( stats.file );
  1827. $('#files_label_hidden_files').html( filesLabel + hiddenFiles );
  1828. $('#stats_details_container').empty().append( details_rows );
  1829. }
  1830. // ***** END DIR_LIST SETUP ***** //
  1831. const $body = $('body');
  1832.  
  1833. // ***** UI SETUP ***** //
  1834. // UI Setup: build UI and add body classes and other initial settings
  1835. function setupUIprefs() {
  1836. for ( let key in $settings ) {
  1837. switch(true) {
  1838. case getQuery(key) === 'true':
  1839. $body.addClass(key);
  1840. if ( getQuery('toggle_sidebar') === 'true' ) { $body.addClass( 'has_hidden_sidebar' ); }
  1841. if ( getQuery('show_invisibles') === 'true' ) { $('#show_invisibles').attr('checked','checked'); }
  1842. break;
  1843. default: // non-boolean settings and others
  1844. switch(true) {
  1845. case key === 'theme':
  1846. $body.addClass( 'theme_'+ getQuery('theme') );
  1847. break;
  1848. case key === 'sort_by':
  1849. $body.addClass( 'sort_by_'+ getQuery(key) );
  1850. $('#sort_by_'+ getQuery(key) ).addClass('selected');
  1851. if ( getQuery('sort_direction') === 'up' ) { $('#sort_by_'+ getQuery(key) ).addClass('up'); }
  1852. break;
  1853. case key === 'default_text_view':
  1854. if ( getQuery(key) === 'source_text' ) { $body.addClass('source_text'); } else { $body.addClass('preview_text'); }
  1855. break;
  1856. }
  1857. if ( getQuery('editor_theme') !== '' ) { $body.addClass( 'editor_theme_'+ getQuery('editor_theme') );
  1858. }
  1859. }
  1860. }
  1861. switch( getBrowser() ) {
  1862. case 'chrome': $body.addClass('is_chrome'); break;
  1863. case 'firefox': $body.addClass('is_gecko'); break;
  1864. case 'safari': $body.addClass('is_safari'); break;
  1865. }
  1866. if ( navigator.platform.indexOf('Win') > -1 ) { $body.addClass('is_windows'); }
  1867. if ( $protocol !== 'file:' ) { $body.addClass('is_non_local'); }
  1868. }
  1869.  
  1870. // Build UI: Append all assembled elements to $body
  1871. function buildUI() {
  1872. if ( window.self === window.top ) { // if it's not an iframe...
  1873. document.title = 'Index of: '+ $location;
  1874. $('head').prepend('<meta charset="utf-8"><base href="'+ document.baseURI +'">');// .find('#title').removeAttr('id');
  1875. if ( $location.startsWith('file') ) { // add favicon for local directories
  1876. let favicon = 'iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUmRcmZzP8zmf8pVcWPAAAAAXRSTlMAQObYZgAAAFBJREFUeF7tyqERwDAMBEE3mX5UiqDmqwwziTPHjG7xrmzrLFtRaApDIRiKQlMYCsFQFJrCUAiGotAU5hTA1WB4fhkMBsOJwWAwgHvB8CHpBcTbpxy4RZNvAAAAAElFTkSuQmCC';
  1877. $('head').prepend('<link href="data:image/png;base64,' + favicon +'" rel="icon" sizes="16x16" />');
  1878. }
  1879. const newIndex = makeNewIndex('body'); // make index
  1880. $main_content.find('#tbody').append(newIndex); // append index to body
  1881. $('body').empty().attr('id','top').attr('lang','en').append($main_content);
  1882. // setup
  1883. if ( !$('body').hasClass('is_error') ) {
  1884. getIndexStats();
  1885. setupUIprefs();
  1886. addStyles( $('head') );
  1887. initMedia();
  1888. autoLoadFile();
  1889. setContentTitle( $('tr.loaded').attr('data-kind') );
  1890. } else {
  1891. $('#content_pane').addClass('has_iframe');
  1892. $('#content_iframe').attr('src',$location +'/?is_error=true').addClass('has_content');
  1893. }
  1894. } else { // set up iframe
  1895. setUpIframeUI();
  1896. }
  1897. }
  1898. buildUI();
  1899.  
  1900. // Get DOMTokenList of body classes
  1901. function getClassList(id) { return document.getElementById(id).classList; }
  1902. // Set up iFrame UI
  1903.  
  1904. // SET UI TO DEFAULT SETTINGS: remove queries;
  1905. function defaultSettings() {
  1906. let query_str = '?';
  1907. if ( getQuery('selected') !== undefined ) { query_str += 'selected='+ getQuery('selected'); }
  1908. if ( getQuery('history') !== undefined ) { query_str += 'history='+ getQuery('history'); }
  1909. query_str = query_str.replace(/\s/g,'+');
  1910. window.location.assign($location + query_str);
  1911. }
  1912. $('#default_settings').on('click', function(e) {
  1913. e.preventDefault(); e.stopPropagation();
  1914. $('body').removeClass('has_menu');
  1915. if (window.confirm( 'Are you sure you want to remove all your temporary UI settings from the URL query string?' ) ) { defaultSettings(); }
  1916. });
  1917. // SAVE USER SETTINGS
  1918. function saveSettings(filename, data) { saveFile(data,'application/json',filename); }
  1919. $('#export_settings').on('click',function(e) {
  1920. e.preventDefault(); e.stopPropagation();
  1921. $('body').removeClass('has_menu');
  1922. const $settings_string = ( JSON.stringify($settings,null,'\t'));
  1923. saveSettings('settings.json',$settings_string);
  1924. });
  1925. // Show/Close Help
  1926. function showHelp() { $('body#top').addClass('has_help'); }
  1927. $('#show_help').on('click','span', function(e) { e.stopPropagation(); showHelp(); });
  1928. $('#close_help').on('click', function(e) { e.preventDefault(); $('body').removeClass('has_help'); });
  1929. $('#help_container').on('click', function(e) { e.stopPropagation(); });
  1930. // Click Menu Bookmark (with warning)
  1931. function setLocation(link) { window.location = link; }
  1932. $('#parent_dir_nav, #parents_dir_nav + .menu, #menu .bookmark').on('click','a', function(e) {
  1933. e.preventDefault();
  1934. switch(true) {
  1935. case $(this).attr('href').indexOf('file://') > -1 && $protocol !== 'file:':
  1936. $body.addClass('has_warning').find('#warnings_container').addClass('local_bookmark');
  1937. break;
  1938. case $body.hasClass('has_playlist'):
  1939. case $body.hasClass('has_filelist'):
  1940. closePlaylist();
  1941. break;
  1942. default:
  1943. showWarning( 'setLocation', thisLink($(this)) );
  1944. }
  1945. });
  1946. // Show Menus
  1947. function showMenus(el) {
  1948. sendMessage('iframe','top_has_menu');
  1949. $('body').removeClass('has_hidden_sidebar');
  1950. let position = $(el).position(), id = $(el).attr('id');
  1951. if ( $('body').hasClass('has_stats') ) { $('#stats').click(); }
  1952. $(el).find('> ul').css({'top':position.top + $(el).innerHeight() + 1 + 'px'});
  1953. if ( id === 'parents_dir_menu' ) {
  1954. if ( $('body').hasClass('has_menu_parents') ) {
  1955. $('body').removeClass('has_menu_parents');
  1956. } else {
  1957. $('body').addClass('has_menu_parents').removeClass('has_menu');
  1958. }
  1959. }
  1960. if ( id === 'menu_container' ) {
  1961. if ( $('body').hasClass('has_menu') ) {
  1962. $('body').removeClass('has_menu');
  1963. sendMessage('iframe','top_closed_menu');
  1964. } else {
  1965. $('body').addClass('has_menu').removeClass('has_menu_parents');
  1966. sendMessage('iframe','top_has_menu');
  1967. }
  1968. }
  1969. }
  1970. $('#parents_dir_menu, #menu_container').on('click',function(e) { e.stopPropagation(); showMenus($(this)); });
  1971. // Click menu
  1972. function clickMenu() {
  1973. $('#menu').find('.selected:not(.hovered)').removeClass('selected').find('a,> span,label').click();
  1974. $('#menu').find('.hovered').removeClass('selected hovered');
  1975. $('body').removeClass('has_menu');
  1976. if ( $('body').hasClass('focus_content') ) { sendMessage('iframe','close_menu'); $('#content_iframe').focus(); }
  1977. if ( $body.hasClass('focus_content') ) { focusContent(); }
  1978. }
  1979. // Close menus
  1980. function closeMenus() {
  1981. if ( !$('body').hasClass('has_menu') && !$('body').hasClass('has_menu_parents') && !$('body').hasClass('has_stats') ) {
  1982. return;
  1983. } else {
  1984. $('body').removeClass('has_menu has_menu_parents faded has_stats has_help');
  1985. }
  1986. if ( $body.hasClass('focus_content') ) { focusContent(); }
  1987. }
  1988. // Show Stats
  1989. function showStats() { $('body').addClass('has_stats').removeClass('has_menu has_menu_parents'); }
  1990. $('#stats_summary').on('click', function(e) { e.stopPropagation(); showStats(); });
  1991. $(document).on('click', function(e) { e.stopPropagation(); closeMenus(); });
  1992.  
  1993. // Toggle UI Preferences
  1994. function toggleUIpref(prefID) {
  1995. let prefClass = prefID;
  1996. switch(prefID) {
  1997. case 'sort_by_name': case 'sort_by_default': case 'sort_by_size': case 'sort_by_date': case 'sort_by_kind': case 'sort_by_ext': // sorting
  1998. $('body').removeClass('has_menu sort_by_default sort_by_name sort_by_size sort_by_date sort_by_kind sort_by_ext');
  1999. break;
  2000. case 'theme': // theme
  2001. if ( $('body').hasClass('theme_dark') ) { prefID = 'theme_dark'; } else { prefID = 'theme_light'; }
  2002. prefClass = 'theme_dark theme_light';
  2003. sendMessage('iframe',prefID);
  2004. break;
  2005. case 'source_text': case 'preview_text': // text editing default view
  2006. prefClass = 'preview_text source_text';
  2007. prefID = 'default_text_view';
  2008. break;
  2009. case 'split_view': case 'alternate_background': case 'ignore_ignored_files': case 'show_invisibles': case 'show_numbers':
  2010. sendMessage('iframe',prefID);
  2011. break;
  2012. }
  2013. toggleQuery(prefID);
  2014. $('body#top').toggleClass(prefClass);
  2015. if ( prefID === 'disable_text_editing' ) { // needs to be here and not in switch above
  2016. $('.selected.text, .selected.markdown, .selected.code').find('a').click();
  2017. }
  2018. scrollThis('tbody','selected',false); // true = instant scroll
  2019. $('#stats').html(getIndexStats($('#dir_list').find('#tbody tr')));
  2020. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  2021. }
  2022. // Click Toggle UI Pref elements
  2023. $('.toggle_UI_pref').on('click',function(e) {
  2024. if ( !$(this).is('input') ) { e.preventDefault(); } // allow checkboxes to be checked
  2025. if ( !$(this).hasClass('disabled') ) { toggleUIpref( $(this).attr('id') ); }
  2026. });
  2027.  
  2028. // Toggle Sidebar
  2029. function toggleSidebar() {
  2030. $body.toggleClass('has_hidden_sidebar');
  2031. if ( $body.hasClass('has_hidden_sidebar') ) { setQuery('toggle_sidebar','true'); } else { removeQuery('toggle_sidebar'); }
  2032. if ( $body.hasClass('focus_content') ) { focusContent(); }
  2033. scrollThis('tbody','selected',true); // true = instant scroll
  2034. }
  2035. $('#toggle_sidebar').on('click', function(e) { e.stopPropagation(); toggleSidebar(); });
  2036.  
  2037. // RESIZE Sidebar/Content Pane
  2038. function resizeSidebar(f) {
  2039. f.stopPropagation();
  2040. const $sidebar_wrapper = $('#sidebar_wrapper');
  2041. const $startX = f.pageX;
  2042. let $window_width = window.innerWidth;
  2043. let $sidebar_width = $sidebar_wrapper.width();
  2044. $('body').addClass('has_overlay');
  2045.  
  2046. $(document).on('mousemove',function(e) {
  2047. e.stopPropagation(); e.preventDefault();
  2048. const $deltaX = e.pageX - $startX;
  2049. if ( e.pageX > 230 && e.pageX < $window_width - 200 ) {
  2050. $sidebar_wrapper.css({'width':$sidebar_width + $deltaX + 'px'});
  2051. $content_pane.css({'width':($window_width - $sidebar_width) - $deltaX + 'px'});
  2052. }
  2053. scrollThis('tbody','selected',false); // true = instant scroll
  2054. });
  2055. $(document).on('mouseup',function() {
  2056. $('body').removeClass('has_overlay');
  2057. $(document).off('mousemove');
  2058. $sidebar_width = $sidebar_wrapper.width();
  2059. setQuery('width',$sidebar_width);
  2060. if ( $body.hasClass('focus_content') ) { focusContent(); }
  2061. });
  2062. }
  2063. $('#handle').on('mousedown', function(f) { f.stopPropagation(); resizeSidebar(f); });
  2064.  
  2065. // ***** BASIC UI FUNCTIONS ***** //
  2066. // Get dir_list item row
  2067. function thisRow(x) { return $(x).closest('#dir_list > tbody > tr').length > 0 ? $(x).closest('#dir_list > tbody > tr') : $(x).closest('#dir_list > li'); }
  2068. function thisID(x) { return thisRow(x).attr('id'); }
  2069. // Get row link
  2070. function thisLink(x) { return $(x).find('a').length > 0 ? $(x).find('a').attr('href') : $(x).attr('href'); }
  2071. // Get row name
  2072. function thisText(x) { return $(x).find('a span').length > 0 ? decodeURIComponentSafe( $(x).find('a span').text().toLocaleLowerCase() ) : decodeURIComponentSafe( $(x).text().toLocaleLowerCase() ); }
  2073. // get row text
  2074. function thisExt(x) { let $this_name = thisText(x); return $this_name.endsWith('app/') ? 'app' : $this_name.endsWith('/') ? '/' : $this_name.lastIndexOf('.') === -1 ? undefined : $this_name.toLocaleLowerCase().slice( $this_name.lastIndexOf('.') + 1 ); }
  2075. // get Element by ID alias
  2076. function getElById(id) { return $(document.getElementById(id) ); }
  2077.  
  2078. // SET CONTENT HEIGHT
  2079. // window.addEventListener('resize', setContentHeight );
  2080.  
  2081. // SET CONTENT TITLE
  2082. function setContentTitle(kind) {
  2083. if ( kind === undefined ) { return; }
  2084. let $title = $('#title span');
  2085. let $title_text;// = $('tr.loaded').not('.audio').find('td.name span').text();
  2086. switch(true) {
  2087. case kind === 'audio': // if audio, don't set content title
  2088. break;
  2089. case $body.hasClass('has_directory_source'):
  2090. $title_text = $current_dir_path;
  2091. break;
  2092. default:
  2093. $title_text = ( $('tr.selected:not(.audio)').length ? $('tr.selected:not(.audio)').find('.name span').text() : $('tr.loaded:not(.audio)').find('.name span').text() );
  2094. break;
  2095. }
  2096. if ( kind === 'image' ) { setImageDimensions(); }
  2097. $title.empty().html($title_text); // set text
  2098. }
  2099. // Get Image Dimensions
  2100. function getDimensions(link, callback) {
  2101. if ( link !== undefined ) {
  2102. let img = new Image();
  2103. img.src = link;
  2104. img.onload = function() { callback( this.width, this.height ); };
  2105. }
  2106. }
  2107. function setImageDimensions() {
  2108. if ( window.top !== window.self ) {
  2109. return;
  2110. } else if ( $content_pane.attr('data-content') === 'has_image' ) {
  2111. let link = $content_image.find('img').attr('src');
  2112. getDimensions( link, function( width, height ) {
  2113. $('#title span').attr('data-after',' (' + width + 'px × ' + height + 'px) ('+ ( ($content_image.find('img').width()/width)*100 ).toFixed(1) +'%)');
  2114. });
  2115. } else {
  2116. $('#title span').removeAttr('data-after'); // remove image dimensions
  2117. }
  2118. }
  2119. // Scroll Selected Items
  2120. function scrollThis(containerID,scrollElClass,bool) {
  2121. const container = document.getElementById(containerID);
  2122. if ( container.height === 0 ) { return; } // don't scroll hidden elements
  2123. let $scrollEl = ( scrollElClass !== undefined ? container.getElementsByClassName(scrollElClass) : null );
  2124. let $behavior = ( ( bool !== undefined || bool === true || $content_pane.attr('data-content') === 'has_grid' ) ? 'instant' : 'smooth' ); // instant allows sidebar & grid to scroll simultaneously
  2125. let $block = ( $('body').hasClass('is_gecko') ? 'start' : 'nearest' );
  2126. if ( $scrollEl !== undefined && $scrollEl.length === 1 ) {
  2127. $scrollEl[0].scrollIntoView({ behavior:$behavior, block:$block, inline:'nearest' });
  2128. }
  2129. }
  2130. // ***** SORTING ***** //
  2131. function sortIndex(els,id,sortDirection) { // id = sort type
  2132. let sorted = [];
  2133. const newSort = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); // needs to be above makeNewIndex()
  2134. sorted = els.removeClass('sorted border_top').sort((a, b) => {
  2135. let aName, bName;
  2136. if ( $('body').hasClass('has_playlist') || $('body').hasClass('has_filelist') ) {
  2137. aName = $(a).attr('id'); bName = $(b).attr('id');
  2138. } else {
  2139. aName = $(a).find('td.name').data('name'); bName = $(b).find('td.name').data('name');
  2140. }
  2141. let aData = $(a).find('td[data-'+ id +']').data(id);
  2142. let bData = $(b).find('td[data-'+ id +']').data(id);
  2143. if ( sortDirection === 1 ) { // sort by detail data value, then by name (i.e., if data values are the same, sort by name)
  2144. if ( newSort.compare(aData, bData) === 0 ) { return newSort.compare(aName, bName); } else { return newSort.compare(aData, bData); }
  2145. } else { // reverse sort
  2146. if ( newSort.compare(bData, aData) === 0 ) { return newSort.compare(bName, aName); } else { return newSort.compare(bData, aData); }
  2147. }
  2148. });
  2149. sorted = sorted.sort((a, b) => { // add sorted style (rules between different rows)
  2150. if ( id === 'kind' || id === 'ext' && sortDirection === 1 ) {
  2151. if ( $(a).find('td[data-'+ id +']').data(id) !== $(b).find('td[data-'+ id +']').data(id) ) { $(a).addClass('sorted border_top'); }
  2152. }
  2153. if ( id === 'kind' || id === 'ext' && sortDirection === -1 ) {
  2154. if ( $(b).find('td[data-'+ id +']').data(id) !== $(a).find('td[data-'+ id +']').data(id) ) { $(a).addClass('sorted border_top'); }
  2155. }
  2156. });
  2157. return sorted;
  2158. }
  2159. // Sort the Dir List on click
  2160. function sortDirList(rows, id, sortDirection) {
  2161. const $sort_all = rows;
  2162. const $sort_dirs = $sort_all.filter('.dir:not(.app)');
  2163. const $sort_files = $sort_all.filter('.file,.app');
  2164. const $sort_id = id.slice(8);// === 'default' ? 'name' : id;
  2165. let $sorted = [];
  2166.  
  2167. if ( $sort_id === 'default' || ( $sort_id !== 'name' && $settings.dirs_on_top === true )) {
  2168. const $sorted_dirs = sortIndex($sort_dirs, $sort_id, sortDirection);
  2169. const $sorted_files = sortIndex( $sort_files, $sort_id, sortDirection );
  2170. if (sortDirection === 1) {
  2171. $sorted = $.merge($sorted_dirs,$sorted_files);
  2172. } else {
  2173. $sorted = $.merge($sorted_files,$sorted_dirs);
  2174. }
  2175. } else {
  2176. $sorted = sortIndex( $sort_all, $sort_id, sortDirection );
  2177. }
  2178. return $sorted;
  2179. }
  2180. // Sort on click
  2181. function clickSort(id) {
  2182. const el = $(getElById(id)); // id from th sort item = 'sort_by_xxx'
  2183. let sortDirection, querySortDirection;
  2184. switch(true) {
  2185. case !el.hasClass('selected'): // el is not selected
  2186. $('#sorting_row_1,#sorting_row_2').find('> div').removeClass('up selected');
  2187. el.addClass('selected');
  2188. sortDirection = 1;
  2189. break;
  2190. case el.hasClass('selected'):
  2191. switch(true) {
  2192. case !el.hasClass('up'): el.addClass('up'); sortDirection = -1; break;
  2193. case el.hasClass('up'): el.removeClass('up'); sortDirection = 1; break;
  2194. }
  2195. }
  2196. querySortDirection = ( sortDirection !== -1 ? 'default' : 'up' );
  2197. setQuery('sort_direction',querySortDirection);
  2198. // SORT EM!
  2199. let rows = $('#tbody').find('tr');
  2200. const $sorted_index = sortDirList( rows, id, sortDirection );
  2201. $dir_list_body.empty().append($sorted_index);
  2202. switch(true) {
  2203. case $content_pane.hasClass('has_playlist'):
  2204. case $content_pane.hasClass('has_filelist'):
  2205. break;
  2206. case $content_pane.hasClass('has_font_grid'): $('#show_font_grid').click(); break;
  2207. case $content_pane.hasClass('has_image_grid'): $('#show_image_grid').click(); break;
  2208. case $content_pane.attr('data-content') === 'has_grid': $('#grid_btn').click(); break;
  2209. }
  2210. scrollThis('tbody','selected',false); // true = instant scroll
  2211. }
  2212. // Sort on clicking dir_list sort item
  2213. $('#sorting').on('click','.sorting, #play_toggle',function(e) {
  2214. e.preventDefault(); e.stopPropagation();
  2215. switch(true) {
  2216. case $(this).attr('id') === 'play_toggle': toggleAllChecked(); break; // toggle media checkboxes
  2217. case !$(this).hasClass('disabled'): clickSort( $(this).attr('id') ); break; // sort dir_list
  2218. }
  2219. });
  2220. // Sort Menu: click the list header that contains the selected menu text.
  2221. $('#sort_menu').on('click','li:not(.disabled)',function(e) {
  2222. e.preventDefault(); e.stopPropagation();
  2223. $('#sorting div[id="sort_by_'+ $(this).attr('id') +'"]').click(); // click corresponding sidebar sort item
  2224. });
  2225. // ***** END SORTING ***** //
  2226. // ***** END BASIC UI FUNCTIONS ***** //
  2227.  
  2228. // ***** CONTENT PANE ***** //
  2229. // Focus Sidebar
  2230. function focusSidebar() {
  2231. document.activeElement.blur();
  2232. $('body').removeClass('faded focus_content').find('#sidebar').focus();
  2233. }
  2234. // Focus content
  2235. function focusContent(id) {
  2236. $('body').addClass('focus_content');
  2237. switch(true) {
  2238. case $content_pane.attr('data-content') === 'has_dir':
  2239. case $content_iframe.hasClass('has_content'):
  2240. $('#content_iframe').focus();
  2241. break;
  2242. case $content_pane.attr('data-content') === undefined:
  2243. case $content_pane.attr('data-content') === 'has_video':
  2244. case $content_pane.attr('data-content') === 'has_pdf':
  2245. case $('body').hasClass('has_playlist'):
  2246. $('body').removeClass('focus_content'); // don't focus content in the above cases
  2247. break;
  2248. case id !== undefined:
  2249. document.getElementById(id).focus();
  2250. break;
  2251. case $content_pane.attr('data-content') === 'has_grid':
  2252. $('#content_grid').focus();
  2253. break;
  2254. case $content_pane.attr('data-content') === 'has_text_editor':
  2255. $('body').addClass('split_view');
  2256. $('#text_source').focus();
  2257. break;
  2258. case $content_pane.attr('data-content') === 'has_image':
  2259. $('#content_image').find('img').focus();
  2260. break;
  2261. case $content_pane.attr('data-content') === 'has_font':
  2262. $('#specimen').focus();
  2263. break;
  2264. case $content_pane.attr('data-content') === 'has_text':
  2265. sendMessage('iframe','tab_iframe'); // enter iframe and focus text_source
  2266. break;
  2267. case $content_pane.attr('data-content') === 'has_text':
  2268. case $content_pane.attr('data-content') === 'has_markdown':
  2269. sendMessage('iframe','iframe_focus_text');
  2270. break;
  2271. }
  2272. }
  2273. function focusButton(id) {
  2274. let el = document.getElementById(id);
  2275. el.classList.add('focus');
  2276. el.focus();
  2277. }
  2278. // Show Grid
  2279. function showGrid(id) {
  2280. if ( id !== undefined ) { makeGrids(id); } // initial make grid items; otherwise, just unhide existing grid (see below)
  2281. closeContent();
  2282. const selectedID = $('#tbody').find('.selected.image, .selected.font').attr('id');
  2283. $('#grid_btn').addClass('has_grid');
  2284. $('#content_pane').removeClass('has_hidden_grid').attr('data-content','has_grid').find('div[data-id="'+ selectedID +'"]').addClass('selected').siblings().removeClass('selected hovered');
  2285. focusContent();
  2286. }
  2287. // Hide Grid
  2288. function hideGrid() { if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_pane.removeAttr('data-content').addClass('has_hidden_grid'); } }
  2289. // Show Hidden Grid
  2290. function showHiddenGrid() { if ( $content_pane.hasClass('has_hidden_grid') ) { showGrid(); } }
  2291. // Close Grid (remove grid elements, etc.)
  2292. function closeGrid() {
  2293. $('#grid_btn').removeClass('has_grid');
  2294. if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_pane.removeAttr('data-content'); }
  2295. $content_pane.removeClass('has_image_grid has_font_grid').find('#content_grid').attr('style','').empty();
  2296. }
  2297. // Show Text Editor
  2298. function showTextEditor() {
  2299. switch(true) {
  2300. case $content_pane.attr('data-content') === 'has_text_editor': // hide open text editor
  2301. hideTextEditor();
  2302. break;
  2303. case !$body.hasClass('has_text_editor_UI'): // add the text editor UI if loading text editor for first time
  2304. setUpTextEditorUI();
  2305. default: // show editor
  2306. closeContent();
  2307. $content_pane.removeClass('has_hidden_text_editor').attr('data-content','has_text_editor'); // empty title
  2308. $('#text_editor_row').addClass('has_text_editor');
  2309. focusContent();
  2310. }
  2311. }
  2312. // Hide Text Editor
  2313. function hideTextEditor() { if ( $content_pane.attr('data-content') === 'has_text_editor' ) { $content_pane.removeAttr('data-content').addClass('has_hidden_text_editor'); } }
  2314. // Show Hidden Text Editor
  2315. function showHiddenTextEditor() { if ( $content_pane.hasClass('has_hidden_text_editor') ) { showTextEditor(); } }
  2316. // showIndexSource();
  2317.  
  2318. //***** SHOW CONTENT *****//
  2319. function showAudio(rowID) {
  2320. closeMedia('video');
  2321. let row = getElById(rowID), link = row[0].children[0].children[0].href, title = row[0].children[0].children[0].innerText;
  2322. row.addClass('playing selected').siblings('.media').removeClass('playing selected');
  2323. $audio_player.attr('src', link );
  2324. $content_pane.addClass('has_audio').removeClass('has_iframe_audio');
  2325. $('#content_audio_title').find('span').empty().text( title );
  2326. $('#content_audio_playlist').removeClass('has_content');
  2327. }
  2328. // Show Font (and create font items)
  2329. function showFont(row,bool,sheet,link) { // bool = true if for show font grid, sheet defined at fontGridItems(); link = from previewed directory
  2330. let fontStyles = $font_styles.sheet;
  2331. const $font_family = (row !== '' ? thisText(row) : link.slice(link.lastIndexOf('/') + 1,link.lastIndexOf('.')) );
  2332. const $font_url = (row !== '' ? thisLink(row) : link);
  2333. if ( bool === false ) { // just show selected font
  2334. if ( fontStyles.cssRules.length > 0 ) { fontStyles.deleteRule(0); } // delete previous @font-face rule
  2335. fontStyles.insertRule('@font-face { font-family: "'+ $font_family +'"; src: url("'+ $font_url +'"); }');
  2336. $content_font.css({ 'font-family':'"'+ $font_family +'"' }); // set content font style
  2337. } else { // else make font grid items
  2338. const $font_grid_item_el = $('<div class="font_grid_item border_top_x background_color_EE_22" data-id="'+ $(row).attr('id') +'"></div>');
  2339. let grid_item = $font_grid_item_el.clone();
  2340. const $display_name = $font_family;
  2341. sheet.insertRule('@font-face { font-family: "'+ $font_family +'"; src: url("'+ $font_url +'"); }');
  2342. 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>' );
  2343. return grid_item;
  2344. }
  2345. }
  2346. // OPEN FONT FILE (req: opentype.js font parsing)
  2347. $('#open_font_label').on('click',function(e) { e.stopPropagation(); });
  2348. // Open font
  2349. $('#menu').on('change','#open_font',function(e) { $('body').removeClass('has_menu faded'); openFile(e,'font'); });
  2350. // Open Font File
  2351. function openFontFile(files,reader) {
  2352. closeContent();
  2353. hideGrid();
  2354. $content_pane.attr('data-content','has_font_file');
  2355. $content_font.addClass('has_content');
  2356. parseFont(reader.result);
  2357. $('#title span').html( files.name );
  2358. $('#open_font').val(''); // reset input to allow same font to be reopened immediately after closing.
  2359. focusContent();
  2360. }
  2361. // Parse font
  2362. function parseFont(fontblob) {
  2363. let font = window.opentype.parse(fontblob);
  2364. getFontInfo(font);
  2365. let $glyphs_container = $('#glyphs_container');
  2366. $glyphs_container.empty();
  2367. let $glyph_container = $('<div class="glyph_container background_color_EE_22 border_right_x border_bottom_x"></div>');
  2368. let $glyph_canvas = $('<canvas class="glyph invert" width="120" height="120"></canvas>');
  2369. let $glyph_info = $('<div class="glyph_info text_color_333"></div>');
  2370. let $glyph_viewer = $('#glyph_viewer');
  2371. $glyph_viewer.data('data-font-name',font.names.fullName.en);
  2372. let glyphs = font.glyphs;
  2373. $content_font.data('data-glyphs',glyphs); // add glyphs data to $content_font
  2374. // Draw glyphs
  2375. for ( let i = 0; i < glyphs.length; i++ ) {
  2376. let glyph = glyphs.glyphs[i];
  2377. // Glyph width
  2378. let boundingBox = glyph.getBoundingBox();
  2379. let glyphWidth = boundingBox.x2 - boundingBox.x1;
  2380. let contextX = (60 - glyphWidth/24);
  2381. // Add glyph info and append elements
  2382. let glyphUnicode = ( glyph.unicode !== undefined ? '#'+ glyph.unicode : glyph.unicode );
  2383. let glyphContainer = $glyph_container.clone();
  2384. let glyphCanvas = $glyph_canvas.clone();
  2385. let glyphInfo = $glyph_info.clone();
  2386. glyphInfo.text(glyph.index +': '+ glyph.name +', '+ glyphUnicode);
  2387. glyphContainer.attr('id','glyph_container_'+ glyph.index ).attr('data-id','glyph_container_'+ glyph.index);
  2388. glyphContainer.append( glyphCanvas.clone().attr('id','glyph_'+ glyph.index ) ).append(glyphInfo);
  2389. $glyphs_container.append( glyphContainer );
  2390. // Draw glyph
  2391. let thisGlyph = document.getElementById('glyph_'+ glyph.index);
  2392. $(thisGlyph).data('contextX',contextX);
  2393. let context = thisGlyph.getContext('2d');
  2394. glyph.draw(context, contextX, 84, 72);
  2395. }
  2396. }
  2397. // Get font info
  2398. function getFontInfo(font) {
  2399. let $font_names = font.names;
  2400. 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>');
  2401. for ( let name in $font_names ) {
  2402. let value = $font_names[name].en;
  2403. if ( name.endsWith('URL') ) {
  2404. let href = value;
  2405. if ( !value.startsWith('http') ) { href = 'http://'+ value; } // in case url without protocol is used
  2406. value = '<a class="text_color_111" href="'+ href +'" target="_blank">'+ value +'</a>';
  2407. }
  2408. $font_info.find('tbody').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>');
  2409. }
  2410. let numGlyphs = font.numGlyphs; // glyph count
  2411. $font_info.find('tbody').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">'+ numGlyphs +'</td></tr>');
  2412. $content_font.find('#font_viewer').prepend($font_info);
  2413. }
  2414. // Show glyph viewer
  2415. $content_font.on('click','.glyph',function(e) {
  2416. e.stopPropagation();
  2417. let id = $(this).attr('id');
  2418. $(this).parent('div').addClass('selected').siblings().removeClass('selected');
  2419. showGlyph( id );
  2420. });
  2421. function showGlyph(id) {
  2422. let $glyph_viewer = $('#glyph_viewer');
  2423. let glyphX = $('#'+ id).data('contextX');
  2424. id = id.slice(id.lastIndexOf('_') + 1); // convert id to number only
  2425. let glyphs = $content_font.data('data-glyphs'); // get glyphs
  2426. let glyph = glyphs.get(id);
  2427. let glyphName = glyph.name;
  2428. let glyphPath = glyph.getPath(glyphX,84,72);
  2429. let glyphSVG = glyphPath.toSVG().replace(/"/g,'\'');
  2430. // set the background SVG image:
  2431. let SVGprefix = '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>';
  2432. $glyph_viewer.show().css({'background-image': SVGprefix + glyphSVG +'</g></svg>")'});
  2433. $glyph_viewer.data('data-raw-svg',glyphSVG).data('data-glyph-name',glyphName).find('#glyph_viewer_info div').text(id +': '+ glyphName +', #'+ glyph.unicode ); // for saving SVG
  2434. $content_pane.attr('data-content','has_glyph');
  2435. }
  2436. // Save glyph svg
  2437. function saveGlyph() {
  2438. let filename = $('#glyph_viewer').data('data-font-name') +'—'+ $('#glyph_viewer').data('data-glyph-name') +'.svg';
  2439. 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>';
  2440. let data_suffix = '</g></svg>';
  2441. let data = data_prefix + $('#glyph_viewer').data('data-raw-svg') + data_suffix;
  2442. saveFile(data,'image/svg+xml',filename);
  2443. }
  2444. $content_font.on('click','#save_svg',function(e) { e.stopPropagation(); saveGlyph(); });
  2445. // END OPEN FONT
  2446. // show PDF
  2447. function showPDF(link) {
  2448. let $embed = '<embed id="content_pdf" class="content" name="plugin" tabindex="0" data-kind="pdf"></embed>';
  2449. $('#content_pdf').remove();
  2450. $content_image.after($embed); // replace existing embed element to allow new pdf content
  2451. $('#content_pdf').addClass('has_content').attr('src',link);
  2452. }
  2453. // Set Content Pane classes
  2454. function setContentClasses(content_el,kind) {
  2455. if ( content_el === 'audio' ) {
  2456. $content_pane.addClass('has_audio');
  2457. } else {
  2458. $content_pane.attr('data-content','has_'+ kind); // remove classes, excluding grid and text classes; add content class
  2459. }
  2460. $dir_list.removeClass('has_dir');
  2461. }
  2462. // get query string for various content kinds
  2463. function getLinkQueries(kind) {
  2464. let query_str = '';
  2465. switch(kind) {
  2466. case 'code': case 'markdown': case 'text':
  2467. query_str = '?split_view='+ getQuery('split_view') +'&default_text_view='+ getQuery('default_text_view') +'&theme='+ getQuery('theme') +'&editor_theme='+ getQuery('editor_theme');
  2468. if ( !$body.hasClass('disable_text_editing') ) { query_str += '&disable_text_editing=false'; } else { query_str += '&disable_text_editing=true'; }
  2469. break;
  2470. case 'app':
  2471. case 'dir':
  2472. query_str = '?sort_by=' + getQuery('sort_by') +'&show_numbers='+ getQuery('show_numbers') +'&use_custom_icons='+ getQuery('use_custom_icons') +'&show_invisibles='+ getQuery('show_invisibles') +'&ignore_ignored_files='+ getQuery('ignore_ignored_files') +'&alternate_background='+ getQuery('alternate_background') +'&theme='+ getQuery('theme');
  2473. break;
  2474. case 'view_directory_source':
  2475. query_str = '?&view_directory_source=true';
  2476. break;
  2477. case 'pdf':
  2478. query_str = '#view=fitB&scrollbar=1&toolbar=1&navpanes=1';
  2479. break;
  2480. }
  2481. return query_str;
  2482. }
  2483. // create link for various content kinds
  2484. function setContentSources(row,link,kind,content_el) {
  2485. let query_str = getLinkQueries(kind);
  2486. switch(true) { // create source link for content
  2487. case kind === 'font' && $('body').attr('id') === 'top':
  2488. showFont(row,false);
  2489. break;
  2490. case kind === 'code': case kind === 'markdown': case kind === 'text':
  2491. link = thisLink(row) + query_str;
  2492. break;
  2493. case kind === 'app':
  2494. case kind === 'dir':
  2495. $content_pane.toggleClass('has_iframe has_dir');
  2496. link = link + query_str;
  2497. $content_iframe.attr('data-dir_url',link.slice(0,link.lastIndexOf('/?') + 1)).attr('data-query_str',query_str); // is this needed?
  2498. break;
  2499. case kind === 'view_directory_source':
  2500. link = $location + query_str;
  2501. break;
  2502. case kind === 'pdf':
  2503. link = link + query_str;
  2504. showPDF(link);
  2505. break;
  2506. }
  2507. switch(true) { // add selected, loaded, or playing classes for directory items
  2508. case kind === 'audio': case kind === 'video':
  2509. row.addClass('playing').siblings('.media').removeClass('playing');
  2510. break;
  2511. default:
  2512. row.addClass('loaded').siblings().removeClass('loaded');
  2513. }
  2514. switch(true) { // add content element source and class
  2515. case kind !== 'image':
  2516. $('#content_'+ content_el).addClass('has_content').attr('src',link);
  2517. break;
  2518. default:
  2519. $('#content_image').addClass('has_content').find('img').attr('src',link);
  2520. }
  2521. }
  2522. // SELECT ROW on click and set classes for $content_pane
  2523. function selectThis(rowID) {
  2524. let row = $(getElById(rowID));
  2525. switch(true) {
  2526. case row.hasClass('disabled'):
  2527. break;
  2528. case $content_pane.attr('data-content') === 'has_grid':
  2529. case $content_pane.hasClass('has_hidden_grid'): {// Select corresponding grid item
  2530. const $grid_selected = $content_grid.find('> div[data-id="'+ rowID +'"]');
  2531. row.addClass('selected').siblings().removeClass('selected hovered');
  2532. $grid_selected.addClass('selected').siblings().removeClass('selected');
  2533. break;
  2534. }
  2535. default:
  2536. $body.removeClass('has_directory_source');
  2537. row.addClass('selected').siblings().removeClass('selected hovered'); // remove classes from sibling, but leave .audio with playing
  2538. row.siblings('.video').removeClass('playing');
  2539. }
  2540. }
  2541. // Show Content, hide text editor and grid
  2542. function showContent(id) { // show any content on row click
  2543. let row = getElById(id);
  2544. let kind = row.attr('data-kind');
  2545. switch(true) {
  2546. case row.length === -1 || row.hasClass('disabled'):
  2547. break; // needed for left/right arrow nav when there are no valid items (i.e. images or fonts)
  2548. case row.hasClass('audio') && $('body').attr('id') === 'top': // show audio
  2549. showAudio(id);
  2550. break;
  2551. case id === 'text_editor' || id === 'text_editor_row': // show text editor
  2552. showTextEditor();
  2553. break;
  2554. case id === 'grid_btn' || id === 'show_image_grid' || id === 'show_font_grid': // show grid
  2555. showGrid(id);
  2556. break;
  2557. case ( /code|text|markdown/.test('kind') ): // send stored text selection data to iframe
  2558. sendMessage('iframe','get_text_selection','',[row.attr('data-selection_start'),row.attr('data-selection_end')]);
  2559. case row.hasClass('video'): // close audio when showing video
  2560. closeMedia('audio');
  2561. default: {// all other content: font, image, pdf, video, iframe, dir
  2562. let link = thisLink(row);
  2563. // load dirs and any other kind of content except audio, font, image, pdf, or video into iframe
  2564. let content_el = ( row.hasClass('ignore') ? 'ignored' : !['audio','font','image','pdf','video'].includes(kind) || kind === 'dir' || kind === 'info' ? 'iframe' : kind );
  2565. closeContent(); // Close all content and hide grid or texteditor
  2566. setContentClasses(content_el,kind); // Add "has_[kind]" class to content_pane (replace with data-kind="...")
  2567. setContentSources(row,link,kind,content_el); // Set the src attribute for the content element
  2568. setContentTitle(kind); // Set the title
  2569. }
  2570. }
  2571. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  2572. }
  2573. // Show iframe Dir
  2574. function showIframeDir(args) {
  2575. $('body#top').addClass('faded');
  2576. $content_pane.attr('data-content','has_dir').removeClass('has_iframe_audio').addClass('has_iframe_dir'); // Prevent attempt to play next audio track after dir change.
  2577. $content_iframe.addClass('has_dir').attr('data-iframe_dir_url',args).attr('src',args + getLinkQueries('dir')); // store path of current navigated iframe directory and set iframe source
  2578. $('#title span').empty().html(decodeURIComponentSafe(args));
  2579. }
  2580. // Show Iframe File
  2581. function showIframeFile(args) {
  2582. let link = args[0], kind = args[1], title = decodeURIComponentSafe(link);
  2583. switch(true) {
  2584. case kind === 'audio': // load audio files
  2585. closeMedia('video');
  2586. $audio_player.attr('src', link );
  2587. $content_pane.addClass('has_audio has_iframe_audio');
  2588. $('#content_audio_title span').empty().text( title );
  2589. playPauseMedia();
  2590. break;
  2591. case ['font','image','pdf','video'].includes(kind): // load non-iframe content types
  2592. closeContent();
  2593. switch(kind) {
  2594. case 'font':
  2595. showFont('',false,'',link);
  2596. $('#content_font').addClass('has_content').css({'font-family':link.slice(link.lastIndexOf('/'),link.lastIndexOf('.')) });
  2597. break;
  2598. case 'image':
  2599. $('#content_image').addClass('has_content').find('img').attr('src',link);
  2600. setImageDimensions();
  2601. break;
  2602. case 'pdf':
  2603. showPDF(link);
  2604. break;
  2605. case 'video':
  2606. closeMedia('audio');
  2607. $('#content_video'+ kind).addClass('has_content').attr('src',link);
  2608. break;
  2609. }
  2610. $content_pane.removeClass('has_dir has_iframe_dir').addClass('has_iframe_file').attr('data-content','has_'+ kind);
  2611. $('#title span').text(decodeURIComponentSafe(link));
  2612. break;
  2613. default: // load iframe content
  2614. $content_pane.removeClass('has_dir has_iframe_dir').addClass('has_iframe_file').attr('data-content','has_'+ kind);
  2615. $content_iframe.addClass('has_content').attr('src',link); // set file container link
  2616. $('#title span').text( title );
  2617. focusContent();
  2618. break;
  2619. }
  2620. focusContent();
  2621. }
  2622. // Show source of current sidebar dir in content pane
  2623. function showDirectorySource() {
  2624. if ( $body.hasClass('has_directory_source') ) {
  2625. $('#close_btn').click();
  2626. } else {
  2627. $body.addClass('has_directory_source');
  2628. showContent('view_directory_source');
  2629. }
  2630. }
  2631. $('#view_directory_source').on('click',function(e) { e.stopPropagation(); showWarning( 'showDirectorySource' ); }); // toggle show directory source
  2632. // Show current sidebar dir in content pane
  2633. function openInContentPane() {
  2634. let link = $location + '?sort_by=' + getQuery('sort_by') +'&show_numbers='+ getQuery('show_numbers') +'&use_custom_icons='+ getQuery('use_custom_icons') +'&show_invisibles='+ getQuery('show_invisibles') +'&ignore_ignored_files='+ getQuery('ignore_ignored_files') +'&alternate_background='+ getQuery('alternate_background');
  2635. closeContent();
  2636. $content_pane.attr('data-content','has_dir');
  2637. $('#title').find('span').html($current_dir_path);
  2638. $content_iframe.addClass('has_content has_dir').attr('src',link);
  2639. focusContent();
  2640. }
  2641. $('#open_in_content_pane').on('click',function() { showWarning( 'openInContentPane' ); });
  2642. // Open Previewed Dir in Sidebar (@ receiveMessage() )
  2643. function openInSidebar(args) {
  2644. let $query_str = getQueryPrefs();
  2645. if ($query_str.has('selected') ) { $query_str.delete('selected'); }
  2646. if ($query_str.has('history') ) { $query_str.delete('history'); }
  2647. window.location = args.slice(0,args.lastIndexOf('/?')+1) +'?'+ $query_str.toString();
  2648. }
  2649.  
  2650. //***** CLOSE CONTENT (Close button or Cmd/Ctrl + W) *****//
  2651. // Close Audio/Video
  2652. function closeMedia(type) { // type === audio || video
  2653. let $mediaEl = ( type === 'audio' ? $audio_player : $content_video );
  2654. $mediaEl.trigger('pause').removeAttr('src');
  2655. switch(true) {
  2656. case type === 'audio':
  2657. $('tr.audio.playing').removeClass('playing');
  2658. $('#content_audio_title span').empty();
  2659. $('#content_audio_playlist').removeClass('has_content');
  2660. if ( $content_pane.hasClass('has_iframe_audio') ) {
  2661. focusContent('content_iframe');
  2662. }
  2663. $content_pane.removeClass('has_audio has_iframe_audio');
  2664. sendMessage('iframe','close_iframe_audio');
  2665. break;
  2666. case type === 'video' && $('#content_pane').attr('data-content') === 'has_video':
  2667. $('tr.video.playing').removeClass('playing');
  2668. $('#content_pane').removeAttr('data-content').find('#content_video').removeClass('has_content');
  2669. setContentTitle( $('tr.selected:not(.audio)').attr('data-kind') );
  2670. break;
  2671. }
  2672. }
  2673. $('#close_audio').on('click',function(e) { e.stopPropagation(); closeMedia('audio'); }); // Close Audio button click
  2674. // Close Playlist
  2675. function closePlaylist() {
  2676. $('#tbody').empty().html($('#tbody').data('dir_list')); // restore original dir_list
  2677. $body.removeClass('has_playlist has_filelist has_media has_audio has_video has_fonts has_images').addClass( $('#tbody').data('dataClasses').join(' ') ).find('#sort_by_date,li#date').removeClass('disabled'); // restore orignal body "has" classes
  2678. $('head').find('title').text('Index of: '+ $location); // change window title back to default
  2679. $('#parents_dir_nav').find('> div').empty().html($current_dir_path);
  2680. $dir_list.find('#stats').html(getIndexStats($dir_list.find('#tbody tr')));
  2681. if ( $('#tbody').find('.media').length < 1 ) { $body.removeClass('has_media has_audio has_video'); }
  2682. }
  2683. // Close index source preview
  2684. function closeIndexSource() {
  2685. $body.removeClass('has_directory_source');
  2686. $('.dir.loaded').click();
  2687. }
  2688. // Close opened font file
  2689. function closeFontFile() {
  2690. $body.removeClass('faded focus_content');
  2691. $content_pane.removeAttr('data-content').find('#glyphs_container').empty();
  2692. $('#font_viewer').remove().end().append(ContentFontViewer());
  2693. $('tr.loaded:not(.audio)').click();
  2694. }
  2695. // Close glyph
  2696. function closeGlyph() {
  2697. $('#glyph_viewer').hide().attr('style','');
  2698. $content_pane.attr('data-content','has_font_file');
  2699. $content_font.addClass('has_content');
  2700. scrollThis('glyphs_container','selected');
  2701. }
  2702. // Close iframe File
  2703. function closeIframeFile() {
  2704. let iframe_dir_url = decodeURIComponentSafe($('#content_iframe').attr('data-iframe_dir_url')); // for iframe navigation
  2705. if ( iframe_dir_url === undefined ) { iframe_dir_url = decodeURIComponentSafe($('#content_iframe').attr('data-dir_url')); } // for current sidebar dir
  2706. let query_str ='?sort_by=' + getQuery('sort_by') +'&show_numbers='+ getQuery('show_numbers') +'&use_custom_icons='+ getQuery('use_custom_icons') +'&show_invisibles='+ getQuery('show_invisibles') +'&ignore_ignored_files='+ getQuery('ignore_ignored_files') +'&theme='+ getQuery('theme') +'&alternate_background='+ getQuery('alternate_background');
  2707. $content_pane.removeClass('has_iframe_file '+ $content_pane.attr('data-content')).addClass('has_dir has_iframe_dir').attr('data-content','has_dir').find('.content.has_content').removeClass('has_content');
  2708. $('#content_iframe').attr('src',iframe_dir_url + query_str).addClass('has_content'); // load source directory from data
  2709. $('#title span').html(iframe_dir_url);
  2710. focusContent('content_iframe');
  2711. }
  2712. // Close all .content elements before opening any new .content from sidebar; leave grid and text editor hidden.
  2713. function closeContent() {
  2714. switch(true) {
  2715. case window.top !== window.self:
  2716. break;
  2717. default: // remove sources, data-content, styles, and classes from #content_pane
  2718. $('#title span').removeAttr('data-after').empty(); // empty content title
  2719. hideGrid(); hideTextEditor();
  2720. $content_pane.removeClass('has_zoom_image has_scaled_image').removeAttr('data-content').find('.content.has_content, #content_image img').removeAttr('data-content style src').removeClass('has_content');
  2721. $content_font.find('#font_viewer').remove().end().append(ContentFontViewer());
  2722. closeMedia('video');
  2723. $body.removeClass('faded focus_content');
  2724. $('.playlist_entry_container').removeClass('has_content').find('textarea').val('');
  2725. }
  2726. }
  2727. //////// NEW CLOSE BUTTON FUNCTION: replaces OLDcloseContent()
  2728. function closeButton() {
  2729. switch(true) {
  2730. case $content_pane.attr('data-content') === 'has_grid': // close grid
  2731. closeGrid();
  2732. if ( $content_pane.hasClass('has_hidden_text_editor') ) { showHiddenTextEditor(); } else { focusSidebar(); }// else { showContent( $('tr:not(.audio).selected').attr('id') ); }
  2733. break;
  2734. case $content_pane.attr('data-content') === 'has_text_editor': // hide text editor
  2735. hideTextEditor();
  2736. if ( $content_pane.hasClass('has_hidden_grid') ) { showHiddenGrid(); } else { focusSidebar(); }// else { showContent( $('tr:not(.audio).selected').attr('id') ); }
  2737. break;
  2738. case $body.hasClass('iframe_edited'): // close edited iframe file
  2739. showWarning('closeContent');
  2740. break;
  2741. case $content_pane.hasClass('has_iframe_file'): // close iframe file and reopen parent directory or selected sidebar directory
  2742. closeIframeFile();
  2743. break;
  2744. case $content_pane.hasClass('has_iframe_dir'): // close iframe dir and reopen selected sidebar directory
  2745. $content_pane.removeAttr('data-content').removeClass('has_iframe_dir');
  2746. $('#content_iframe').removeAttr('data-iframe_dir_url').removeAttr('data-iframe_query_str');
  2747. $dir_list.find('.dir.selected a').click();
  2748. focusContent('content_iframe');
  2749. break;
  2750. case $body.hasClass('has_directory_source'): // close directory source
  2751. closeIndexSource();
  2752. break;
  2753. case $content_pane.attr('data-content') === 'has_glyph': // close glyph
  2754. showWarning( 'closeGlyph' );
  2755. break;
  2756. case $content_pane.attr('data-content') === 'has_font_file': // close font file preview
  2757. showWarning( 'closeFontFile' );
  2758. break;
  2759. case $content_pane.hasClass('has_ignored'): // close ignored content
  2760. case $('.content').hasClass('has_content'): // close all other content
  2761. closeContent(); // false = show hidden text editor or grid after closing other content
  2762. focusSidebar();
  2763. $('.loaded').removeClass('loaded'); // don't remove selected class; allows navigation to continue from last selection
  2764. switch(true) {
  2765. case $content_pane.hasClass('has_hidden_grid'): showGrid(); break;
  2766. case $content_pane.hasClass('has_hidden_text_editor'): showTextEditor(); break;
  2767. }
  2768. break;
  2769. case $content_pane.hasClass('has_audio'): // close audio
  2770. closeMedia('audio');
  2771. focusSidebar();
  2772. break;
  2773. case $body.hasClass('has_playlist'): // close playlist
  2774. case $body.hasClass('has_filelist'): // close filelist
  2775. showWarning( 'closePlaylist' );
  2776. break;
  2777. }
  2778. // show hidden texteditor or grid
  2779. }
  2780. $('#close_btn').on('click', function(e) { e.stopPropagation(); e.preventDefault(); showWarning('closeButton'); $(this).blur(); });
  2781. //***** RESET CONTENT (Reset button or Cmd/Ctrl + R) *****//
  2782. function resetContent() {
  2783. $audio_player.prop('currentTime', 0).trigger('pause'); $('#content_audio_playlist').removeClass('has_content');
  2784. switch(true) {
  2785. case $content_pane.attr('data-content') === 'has_grid':
  2786. $('#grid_btn').click();
  2787. break;
  2788. case $content_pane.attr('data-content') === 'has_glyph':
  2789. $('#glyph_viewer').css({'width':'100%','height':'100%','background-size':'100%'}).attr('data-scale',1);
  2790. break;
  2791. case $content_pane.attr('data-content') === 'has_font':
  2792. $content_font.css({'font-size':'1em'});
  2793. break;
  2794. case $content_pane.attr('data-content') === 'has_image':
  2795. $content_pane.removeClass('has_zoom_image has_scaled_image').find('#content_image').removeAttr('style').find('img').removeAttr('style');
  2796. break;
  2797. case $content_pane.attr('data-content') === 'has_text_editor':
  2798. break;
  2799. case $content_pane.attr('data-content') === 'has_video':
  2800. $content_video.prop('currentTime',0).trigger('pause');
  2801. break;
  2802. case $content_pane.hasClass('has_iframe') || $content_pane.hasClass('has_dir'):
  2803. case $content_pane.attr('data-content') === 'has_iframe' || $content_pane.attr('data-content') === 'has_dir':
  2804. $('#tbody').find('.selected').find('a').click();
  2805. break;
  2806. default:
  2807. window.location = window.location.href;
  2808. }
  2809. }
  2810. $('#reload_btn').on('click', function(e) { e.stopPropagation(); e.preventDefault(); showWarning('resetContent'); $(this).blur().removeClass('reset'); });
  2811.  
  2812. //**********************//
  2813. //***** NAVIGATION *****//
  2814. function firstRowID(className) { return ( $('#tbody').find('> tr:visible').filter(className).length ? $('#tbody').find('> tr:visible').filter(className).first().attr('id') : null ); }
  2815. function prevRowID(className) {
  2816. let $selected, rowID;
  2817. if ( /audio|video|media/.test(className) ) { $selected = $('#tbody').find('> tr.playing'); } else { $selected = $('#tbody').find('> tr.selected'); }
  2818. if ( !$selected.length || !$selected.prevAll('tr:visible:not(.disabled)').filter(className).length ) {
  2819. rowID = $('#tbody').find('> tr:visible:not(.disabled)').filter(className).last().attr('id'); // select last item if nothing selected or selected is first item
  2820. } else {
  2821. rowID = $selected.prevAll('tr:visible:not(.disabled)').filter(className).first().attr('id');
  2822. }
  2823. return rowID;
  2824. }
  2825. function nextRowID(className) { // if nothing selected, or if no next row with classname, return first row with classname, else return next row with classname
  2826. let row;
  2827. if ( /audio|video|media/.test(className) ) { row = $('tr.playing'); } else { row = $('#tbody').find('.selected'); }
  2828. return ( !row.length || !row.nextAll('tr:visible').not('.disabled').filter(className).length ) ? $('#tbody').find('> tr:visible').not('.disabled').filter(className).first().attr('id') : row.nextAll('tr:visible').not('.disabled').filter(className).first().attr('id');
  2829. }
  2830. // get selected row id
  2831. function selectRowID(className,key) {
  2832. let id = '';
  2833. switch(key) {
  2834. case 'ArrowUp': id = prevRowID('.dir,.file'); break;
  2835. case 'ArrowLeft': id = prevRowID(className); break;
  2836. case 'ArrowRight': id = nextRowID(className); break;
  2837. case 'ArrowDown': id = nextRowID('.dir,.file'); break;
  2838. }
  2839. return id;
  2840. }
  2841. // NAVIGATION: Menu Arrow Navigation
  2842. function menuNavigation(key) {
  2843. switch(key) {
  2844. case 'ArrowUp':
  2845. switch(true) {
  2846. case $('#menu').find('.hovered').length > 0: // if submenu visible
  2847. if ( !$('#menu').find('ul li.selected').length ) { // if no menu item selected, select last submenu item
  2848. $('#menu').find('ul li:last-of-type').addClass('selected');
  2849. } else { // else select previous submenu item
  2850. $('#menu').find('ul li.selected').removeClass('selected').prev('li').addClass('selected');
  2851. }
  2852. break;
  2853. case !$('#menu').find('.selected').length:
  2854. $('#menu').find('> li:last-of-type').addClass('selected');
  2855. break; // select last item
  2856. default:
  2857. $('#menu').find('.selected:not(.hovered)').removeClass('selected').prev('li').addClass('selected');
  2858. }
  2859. break;
  2860. case 'ArrowDown':
  2861. switch(true) {
  2862. case $('#menu').find('.hovered').length > 0: // if submenu visible
  2863. if ( !$('#menu').find('ul li.selected').length ) { // if no menu item selected, select first submenu item
  2864. $('#menu').find('ul li:first-of-type').addClass('selected');
  2865. } else { // else select next submenu item
  2866. $('#menu').find('ul li.selected').removeClass('selected').next('li').addClass('selected');
  2867. }
  2868. break;
  2869. case !$('#menu').find('.selected').length:
  2870. $('#menu').find('> li:first-of-type').addClass('selected');
  2871. break;
  2872. default:
  2873. $('#menu').find('.selected:not(.hovered)').removeClass('selected').next('li').addClass('selected');
  2874. }
  2875. break;
  2876. case 'ArrowLeft':
  2877. switch(true) {
  2878. case !$('#menu').find('.selected').length:
  2879. $('#menu').find('> li:last-of-type').addClass('selected');
  2880. break;
  2881. case $('#menu').find('li.selected').hasClass('hovered'):
  2882. $('#menu').find('.hovered').removeClass('hovered'); $('#menu').find('ul li').removeClass('selected');
  2883. break;
  2884. }
  2885. break;
  2886. case 'ArrowRight':
  2887. switch(true) {
  2888. case !$('#menu').find('.selected').length:
  2889. $('#menu').find('> li:first-of-type').addClass('selected');
  2890. break;
  2891. case $('#menu').find('li.selected').hasClass('has_submenu') && !$('.submenu').find('.selected').length:
  2892. $('#menu').find('.selected').addClass('hovered').find('ul li:first-of-type').addClass('selected');
  2893. break;
  2894. }
  2895. break;
  2896. }
  2897. }
  2898. // NAVIGATION: select GRID items by left/right arrow keys @ arrowNavigation();
  2899. function gridNavigation(elId,key) {
  2900. const $container_el = $(elId);
  2901. let $selected = $container_el.find('> .selected');
  2902. let grid_item_id; // = grid item data-id = corresponding dir_list item id.
  2903. let rowLength = 0; // $content_grid.hasClass('has_grid') || $('#content_font').hasClass('has_font_grid')
  2904. rowLength = ( Math.round( $container_el.innerWidth() / $container_el.find('> div').outerWidth()) - 1 );
  2905. switch(true) {
  2906. case $selected.length === 0: // nothing selected
  2907. switch(true) {
  2908. case key === 'ArrowRight': case key === 'ArrowDown': $container_el.find('> div').first().addClass('selected'); break;
  2909. case key === 'ArrowLeft': case key === 'ArrowUp': $container_el.find('> div').last().addClass('selected'); break;
  2910. }
  2911. grid_item_id = $container_el.find('.selected').attr('data-id');
  2912. break;
  2913. case key === 'ArrowDown':
  2914. switch(true) {
  2915. case $selected.nextAll().eq(rowLength).length === 1: grid_item_id = $selected.nextAll().eq(rowLength).attr('data-id'); break;
  2916. case $container_el.find('> div').length - 1 === rowLength: gridNavigation(elId,'ArrowRight'); break; // for single grid row, select next item
  2917. default: grid_item_id = $container_el.find('> div').first().attr('data-id'); break;
  2918. }
  2919. break;
  2920. case key === 'ArrowUp':
  2921. switch(true) {
  2922. case $selected.prevAll().eq(rowLength).length === 1: grid_item_id = $selected.prevAll().eq(rowLength).attr('data-id'); break;
  2923. case $container_el.find('> div').length - 1 === rowLength: gridNavigation(elId,'ArrowLeft'); break; // for single grid row, select prev item
  2924. default: grid_item_id = $container_el.find('> div').last().attr('data-id'); break;
  2925. }
  2926. break;
  2927. case key === 'ArrowLeft':
  2928. switch(true) {
  2929. case $selected.prev().length === 1: grid_item_id = $selected.prev().attr('data-id'); break;
  2930. default: grid_item_id = $container_el.find('> div').last().attr('data-id'); break;
  2931. }
  2932. break;
  2933. case key === 'ArrowRight':
  2934. switch(true) {
  2935. case $selected.next().length === 1: grid_item_id = $selected.next().attr('data-id'); break;
  2936. default: grid_item_id = $container_el.find('> div').first().attr('data-id'); break;
  2937. }
  2938. break;
  2939. }
  2940. selectThis(grid_item_id);
  2941. switch(true) {
  2942. case $content_pane.attr('data-content') === 'has_glyph': $('.glyph_container.selected .glyph').click(); break; // show the selected glyph
  2943. case $content_pane.hasClass('has_hidden_grid'): $('#content_grid > div[data-id="'+ grid_item_id +'"]').click(); break;
  2944. }
  2945. }
  2946. // NAVIGATION: FONTS and IMAGES by prev/next buttons
  2947. $content_pane.on( 'click','#prev_btn, #next_btn', function(e) {
  2948. e.stopPropagation(); e.preventDefault();
  2949. let key = $(this).attr('id') === 'prev_btn' ? 'ArrowLeft' : 'ArrowRight';
  2950. if ( $content_pane.attr('data-content') === 'has_font_viewer' ) {
  2951. gridNavigation('#glyphs_container',key);
  2952. } else {
  2953. clickRow( selectRowID('.font,.image',key));
  2954. }
  2955. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  2956. });
  2957. // NAVIGATION: MEDIA 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
  2958. function playPrevNextTrack(key) {
  2959. // context.resume();
  2960. switch(true) {
  2961. case $('#audio').attr('src') === 'unknown' && $('tr.selected').hasClass('audio'):
  2962. showAudio( $('tr.selected').attr('id') );
  2963. break;
  2964. case $content_pane.hasClass('has_iframe_audio'): // play track from navigated directory
  2965. sendMessage('iframe','play_prev_next_iframe_audio','',key);
  2966. break;
  2967. case $('.playing').length === 0: // Arrow L/R selects first/last audio file if nothing selected
  2968. clickRow( selectRowID('.media:not(.unchecked)',key) ); playMedia('play');
  2969. break;
  2970. case $('.playing').length === 1: {
  2971. let mediaClass = ( $('.playing').hasClass('audio') ? '.audio:not(.unchecked)' : '.video:not(.unchecked)' );
  2972. switch(true) {
  2973. case $body.hasClass('shuffle_audio'): { // If shuffle play...
  2974. let trackRowID = $audio_player.data('shufflelist').pop(); // remove track from list
  2975. if ( trackRowID !== undefined ) { // if shuffle list is not empty...
  2976. showAudio( trackRowID ); playMedia('play'); // load media and play
  2977. } else if ( trackRowID === undefined ) { // if end of shufflelist...
  2978. if ( $body.hasClass('loop_audio') ) { // and if loop audio, update the shufflelist and play
  2979. updateShuffleList(); playMedia('play');
  2980. } else { // else just load the first track
  2981. showAudio( firstRowID( mediaClass ));
  2982. }
  2983. }
  2984. break;
  2985. }
  2986. case $(mediaClass).filter('.selected').length === 1 && !$('.media.selected').hasClass('playing'): // else if there is another media file selected, play it next:
  2987. showAudio( $('.media.selected').attr('id') ); playMedia('play');
  2988. break;
  2989. default:
  2990. switch(true) {
  2991. case selectRowID( mediaClass,key ) !== firstRowID( mediaClass ): // show and play next track if next track !== first track
  2992. case $body.hasClass('loop_audio'): // if loop audio, always show and play
  2993. showAudio( selectRowID( mediaClass,key ) );
  2994. playMedia('play');
  2995. break;
  2996. case selectRowID( mediaClass,key ) === firstRowID( mediaClass ): // if next track is first track, just show it
  2997. showAudio( selectRowID( mediaClass,key ) );
  2998. break;
  2999. }
  3000. break;
  3001. }
  3002. }
  3003. }
  3004. }
  3005. // NAVIGATION: Audio by prev/next audio buttons
  3006. $('.prev_next_track_btn').on('click',function() { let key = ( $(this).attr('id') === 'prev_track' ? 'ArrowLeft' : 'ArrowRight' ); playPrevNextTrack(key); });
  3007. function upDownArrowNavigation(e) {
  3008. e.preventDefault();
  3009. let rowID = selectRowID('.dir:visible,.file:visible',e.key);
  3010. let row = $(document.getElementById(rowID));
  3011. let $selected = $('#tbody').find('.selected');
  3012. switch(true) {
  3013. case window.self !== window.top && cmdKey(e): // iframe && cmdKey
  3014. switch(true) {
  3015. case $('#iframe_body #dir_list').length === 0:
  3016. break;
  3017. case e.key === 'ArrowDown': // open selected iframe item
  3018. $selected.find('a').trigger('dblclick');
  3019. break;
  3020. case e.key === 'ArrowUp': // open selected iframe item
  3021. $('#parent a').trigger('click');
  3022. break;
  3023. }
  3024. break;
  3025. case cmdKey(e) && e.key === 'ArrowDown' && $('#tbody').find('.selected').hasClass('dir'): // open selected directory with cmdKey
  3026. if ( $selected.hasClass('app') && $settings.apps_as_dirs === false ) {
  3027. break;
  3028. } else {
  3029. $selected.find('a').trigger('dblclick');
  3030. break;
  3031. }
  3032. case cmdKey(e) && e.key === 'ArrowUp': // Cmd/Ctrl + up arrow = go to parent directory
  3033. showWarning( 'clickThis', $('#parent_dir_nav').attr('id') );
  3034. break;
  3035. case $body.hasClass('has_directory_source'): // if viewing directory source
  3036. if ( $('#tbody .loaded').length > 0 ) { clickRow( $('#tbody .loaded').attr('id') ); } else { clickRow( rowID ); }
  3037. scrollThis('tbody','selected');
  3038. break;
  3039. default: // click prev/next item
  3040. selectThis(rowID); // don't use $selected after this
  3041. switch(true) { // don't show content for media; only select row
  3042. case row.hasClass('audio'):
  3043. case row.hasClass('video') && $('.playing').hasClass('video'):
  3044. break;
  3045. default: showContent(rowID);
  3046. }
  3047. if ( $('.selected').length ) { scrollThis('tbody','selected'); }
  3048. }
  3049. }
  3050. // NAVIGATION: Prev/Next Audio or Prev/Next File of same data-kind by arrow left/right
  3051. function leftRightArrowNavigation(className,e) {
  3052. if ( cmdAltKey(e) && e.key === 'ArrowLeft' || cmdAltKey(e) && e.key === 'ArrowRight' ) { // don't start audio tracks when changing tabs
  3053. return;
  3054. } else {
  3055. e.preventDefault();
  3056. let $selected = $('#tbody').find('.selected');
  3057. switch(true) {
  3058. case e.altKey && e.shiftKey:
  3059. case e.altKey && !e.metaKey && !e.ctrlKey: { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  3060. let args = [e.key];
  3061. if ( e.shiftKey ) { args.push(30); } else { args.push(10); }
  3062. switch(true) {
  3063. case window.top !== window.self: sendMessage('top','mediaSkip','mediaSkip',args); break;
  3064. default: mediaSkip(e);
  3065. }
  3066. break;
  3067. }
  3068. case $('#content_pane').hasClass('has_audio') && window.top === window.self: playPrevNextTrack(e.key); break; // play prev/next audio track
  3069. case $body.hasClass('has_directory_source'): // if viewing directory source
  3070. if ( $('#tbody .loaded').length > 0 ) { clickRow( $('#tbody .loaded').attr('id') ); } else { clickRow( selectRowID('.dir,.file',e.key) ); }
  3071. scrollThis('tbody','selected');
  3072. break;
  3073. default:
  3074. switch(true) {
  3075. case $selected.length === 0: clickRow( selectRowID('.dir,.file',e.key) ); break; // click first/last row
  3076. case $selected.hasClass('audio'): clickRow( selectRowID('.audio',e.key) ); break; // click audio
  3077. case !$selected.hasClass('audio'): clickRow( selectRowID('.'+ $selected.attr('data-kind'),e.key) ); break;
  3078. }
  3079. if (className === 'video') { playMedia('play'); }
  3080. scrollThis('tbody','selected');
  3081. }
  3082. }
  3083. }
  3084. // NAVIGATE directory index items by arrow up/down or left/right keys, with/without grid @ indexNavigation()
  3085. function arrowNavigation(e) {
  3086. switch(true) {
  3087. case window.top !== window.self && document.activeElement.getAttribute('id') === 'iframe_body' && $('#iframe_body').find('#dir_list').length === 0: // allow arrows to work normally in iframes (but not iframe dirs)
  3088. case ( /img|textarea|embed/.test( document.activeElement.tagName.toLowerCase() ) ): // allow arrows to work normally in images and textareas
  3089. case document.activeElement.hasAttribute('contentEditable'): // allow arrows to work normally in contentEditable elements
  3090. break;
  3091. default:
  3092. e.preventDefault(); // otherwise prevent default arrow behavior
  3093. $('.button').blur(); // Need this to able to select grid items after clicking close button (or other buttons).
  3094. case $('body').hasClass('has_menu'): // menu navigation
  3095. if ( window.top !== window.self ) { sendMessage('top','menu_navigation','menuNavigation',e.key); } else { menuNavigation(e.key); }
  3096. break;
  3097. case $content_pane.attr('data-content') === 'has_grid' && $('body').hasClass('focus_content'): // navigate grid
  3098. case $content_pane.attr('data-content') === 'has_image' && $content_pane.hasClass('has_hidden_grid'): // navigate hidden grid
  3099. case $content_pane.attr('data-content') === 'has_font' && $content_pane.hasClass('has_hidden_grid'): // navigate hidden grid
  3100. gridNavigation('#content_grid',e.key);
  3101. if ( !$content_pane.hasClass('has_hidden_grid') ) { scrollThis('content_grid','selected'); }
  3102. scrollThis('tbody','selected');
  3103. break;
  3104. case $content_pane.attr('data-content') === 'has_font_file': // navigate font file glyphs
  3105. case $content_pane.attr('data-content') === 'has_glyph': // navigate font file glyphs
  3106. if ( $('body').hasClass('focus_content') ) {
  3107. gridNavigation('#glyphs_container',e.key);
  3108. scrollThis('content_container','selected');
  3109. } else {
  3110. showWarning('warning_close_font','warning_btn_cancel'); // show close font warning if sidebar focused
  3111. }
  3112. break;
  3113. case e.key === 'ArrowUp' || e.key === 'ArrowDown': // up/down arrow navigation
  3114. upDownArrowNavigation(e);
  3115. break;
  3116. case e.key === 'ArrowLeft' || e.key === 'ArrowRight': { // left/right arrow navigation
  3117. let className = ( $('.selected[data-kind]') === undefined ? $('#dir_list').find('tbody tr:visible').first().attr('data-kind') : $('.selected[data-kind]').attr('data-kind') );
  3118. leftRightArrowNavigation(className,e);
  3119. break;
  3120. }
  3121. }
  3122. }
  3123. // NAVIGATION: by typed string
  3124. var str = '';
  3125. function timeoutID() { return window.setTimeout( function() { str = ''; }, 1500 ); }
  3126. function alphaNav(e) { // Select Dir_List row by typed string; Todo: select next row by nearest letter
  3127. if ( document.activeElement.tagName.toLowerCase() === 'textarea' || document.activeElement.getAttribute('contentEditable') === true ) {
  3128. return;
  3129. } else {
  3130. let timer = timeoutID();
  3131. if ( typeof timer === 'number' ) {
  3132. window.clearTimeout( timer );
  3133. timer = 0; // id
  3134. }
  3135. timeoutID();
  3136. str += e.key;
  3137. str = str.toLowerCase();
  3138. if ( $('#dir_list').find('td.name[data-name^="'+ str +'"]').length ) {
  3139. $('#dir_list').find('td.name[data-name^="'+ str +'"]').first().find('a').click();
  3140. scrollThis('tbody','selected');
  3141. // } else {
  3142. // null; // replace this with some sort of fuzzy match function? TBD
  3143. }
  3144. }
  3145. }
  3146. //***** END NAVIGATION *****//
  3147.  
  3148. // CLICK element by id
  3149. function clickThis(id) { let el = $(document.getElementById(id)); if ( el.find('a').length > 0 ) { el.find('a').click(); } else { el.click(); } }
  3150. // CLICK Row to show content
  3151. function clickRow(id) {
  3152. selectThis(id);
  3153. showContent(id);
  3154. }
  3155. // On click row, play/pause media or clickRow
  3156. $('#top #tbody').on('click','tr', function(e) {
  3157. e.preventDefault(); e.stopPropagation();
  3158. focusSidebar();
  3159. if ( $(this).hasClass('playing') ) {
  3160. playPauseMedia();
  3161. } else {
  3162. showWarning( 'clickRow', $(this).attr('id') );
  3163. }
  3164. });
  3165. // DOUBLE-CLICK Row to open directory
  3166. function doubleClickRow(args) { // row.dir only
  3167. let id = args[0].slice(args[0].indexOf('-') + 1), href = args[1];
  3168. let $query_str = decodeURIComponentSafe(window.location.search);
  3169. if ( $query_str === '' ) { $query_str = '?'; }
  3170. if ( $query_str.indexOf('history') !== -1 ) {
  3171. $query_str = $query_str.replace(/&selected=\d+/,'').replace(/&history=/,'&history='+ id +'+');
  3172. } else {
  3173. $query_str = $query_str.replace(/&selected=\d+/,'') + '&history='+ id;
  3174. }
  3175. window.location = href + $query_str;
  3176. }
  3177. $('#top').on('dblclick', '#tbody tr.dir', function(e) {
  3178. e.preventDefault(); e.stopPropagation();
  3179. showWarning( 'doubleClickRow', [$(this).attr('id'), $(this).find('a').attr('href')] );
  3180. });
  3181. // click content header or audio to remove faded body class
  3182. $('#sidebar_title,#sidebar_buttons,#sidebar,#sorting .sorting').on('click',function(e) { focusSidebar(); });
  3183. // CLICK grid item
  3184. $content_grid.on('click','div', function(e) {
  3185. e.preventDefault();
  3186. let id = $(this).attr('data-id');
  3187. $(this).addClass('selected').siblings().removeClass('selected');
  3188. hideGrid();
  3189. getElById(id).click();
  3190. });
  3191. // HOVER Grid Item
  3192. $content_grid.on('mouseenter','> div:not(.selected)',function() {
  3193. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).addClass('hovered');
  3194. }).on('mouseleave','> div:not(.selected)',function() {
  3195. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).removeClass('hovered');
  3196. });
  3197. // HOVER Dir_list_row: highlight corresponding grid item
  3198. $dir_list_body.find('> tr').hover(function() {
  3199. if ( $content_grid.is(':visible') ) { $content_grid.find('[href*="'+ thisLink(this) +'"]').closest('div').addClass('hovered'); }
  3200. }, function() {
  3201. if ( $content_grid.is(':visible') ) { $content_grid.find('.hovered').removeClass('hovered'); }
  3202. });
  3203. // HOVER Footer Links: Fade dir_list
  3204. $('#footer_links').on('mouseenter',function() {
  3205. $('body:not(.has_menu), body:not(.has_menu_parents)').addClass('faded');
  3206. }).on('mouseleave',function() {
  3207. $('body:not(.has_menu), body:not(.has_menu_parents)').removeClass('faded');
  3208. });
  3209. // HOVER Stats Items: highlight dir_list items
  3210. $('#stats').on('mouseenter','#stats_details_container > div, #stats_summary_detailed_dirs, #stats_summary_detailed_files', function() {
  3211. let thisClass = $(this).attr('class');
  3212. switch(true) {
  3213. case $(this).attr('id') === 'stats_summary_detailed_dirs': thisClass = '.dir'; break;
  3214. case $(this).attr('id') === 'stats_summary_detailed_files': thisClass = '.file'; break;
  3215. case /_/.test(thisClass): thisClass = '.'+ thisClass.replace('_','.'); break;
  3216. default: thisClass = '.'+ thisClass +':not(.invisible):not(.ignore)';
  3217. }
  3218. $('#tbody').find(thisClass).addClass('hovered');
  3219. }).on('mouseleave','div',function() {
  3220. $('#tbody tr').removeClass('hovered').removeAttr('style');
  3221. });
  3222. // END CLICK & HOVER EVENTS
  3223.  
  3224. //***** AUTOLOAD CONTENT: index files or files from the file shortcut list *****//
  3225. function autoLoadFile() {
  3226. let selectedID = ( getQuery('selected').length > 0 ? 'rowid-'+ getQuery('selected') : undefined);
  3227. switch(true) {
  3228. case window.self !== window.top: // do nothing for iframes
  3229. break;
  3230. case $body.hasClass('has_audio') && getQuery('autoload_media') === 'true': // load audio
  3231. clickRow( $('.audio').first().attr('id') ); // load audio
  3232. if ( $('body').hasClass('has_images') ) { autoLoadCoverArt(); } else if ( selectedID !== undefined ) { clickRow( selectedID ); } // autoload cover art or select dir
  3233. break;
  3234. case $body.hasClass('has_video') && getQuery('autoload_media') === 'true': // load video (if no audio)
  3235. clickRow( firstRowID('.video') );
  3236. break;
  3237. case getQuery('file') !== undefined && getQuery('file').length > 0: // load files (from bookmark or url)
  3238. clickRow( $dir_list.find('a[href*="'+ getQuery('file') +'"]').closest('tr').attr('id') );
  3239. removeQuery('file');
  3240. break;
  3241. case getQuery('autoload_index_files') === 'true' && $dir_list.find( 'a[href*="/index."]').length > 0: // else load index file
  3242. clickRow($('a[href*="/index."]').closest('tr').attr('id'));
  3243. break;
  3244. default:
  3245. if ( selectedID !== undefined) {
  3246. showContent( selectedID );
  3247. }
  3248. }
  3249. if ( $('#tbody').find('.selected').length > 0 ) { scrollThis('tbody','selected'); }
  3250. }
  3251. // Autoload Cover Art
  3252. function getImageNames() {
  3253. let image_names = [];
  3254. $dir_list_body.find('.image').each(function() {
  3255. let name = $(this).find('td.name').attr('data-name');
  3256. name = name.slice(0,name.lastIndexOf('.'));
  3257. image_names.push( name );
  3258. });
  3259. return image_names;
  3260. }
  3261. function getCoverArtID() {
  3262. const cover_names = ['cover','front'];
  3263. const image_names = getImageNames();
  3264. const $image_files = $dir_list_body.find('tr.image');
  3265. for ( let cover_name of cover_names ) {
  3266. switch(true) {
  3267. case image_names.includes(cover_name): // file name === a cover_name
  3268. return $image_files.eq( image_names.indexOf(cover_name) ).attr('id');
  3269. case image_names.some( name => name.startsWith(cover_name)): // file name starts with a cover name
  3270. return $image_files.find('td.name[data-name^="'+ cover_name +'"]').closest('tr').attr('id');
  3271. case image_names.some( name => name.indexOf(cover_name) > 0 ): // file name includes a cover name
  3272. return $image_files.find('td.name[data-name*="'+ cover_name +'"]').closest('tr').attr('id');
  3273. default: // else use first image
  3274. return $image_files.first().attr('id');
  3275. }
  3276. }
  3277. }
  3278. function autoLoadCoverArt() { // autoload cover art for audio if dir contains audio and images, and audio is loaded and non-image content is not loaded
  3279. let $coverID = getCoverArtID();
  3280. if ( $coverID !== undefined ) {
  3281. let row = getElById($coverID);
  3282. row.addClass('loaded');
  3283. $content_pane.attr('data-content','has_image').find('#content_image').addClass('has_content').find('img').attr('src',row.find('a').attr('href'));
  3284. setImageDimensions();
  3285. $('#title').find('span').html(row.find('.name').text());
  3286. }
  3287. }
  3288. //***** KEYBOARD EVENTS *****//
  3289. function metaKey(e) { return ( navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey); }
  3290. function cmdKey(e) { return ( metaKey(e) && !e.altKey && !e.shiftKey ); }
  3291. function cmdAltKey(e) { return ( metaKey(e) && e.altKey && !e.shiftKey ); }
  3292. // function cmdAltShiftKey(e) { return ( metaKey(e) && e.altKey && e.shiftKey ); }
  3293. function cmdShiftKey(e) { return ( metaKey(e) && !e.altKey && e.shiftKey ); }
  3294.  
  3295. $('#top, #iframe_body').on('keydown', function(e) {
  3296. const $selected = ( $('#content_pane').attr('data-content') === 'has_font_file' ? $('.glyph_container.selected .glyph') : $('#tbody').find('.selected') );
  3297. // Disable all keydown events except return and tab when help or warning is shown, or when textarea is focused
  3298. if ( ($('body').hasClass('has_warning') || $('body').hasClass('has_help') ) && !( cmdKey(e) || metaKey(e) || e.key === 'shiftKey' || e.key === 'Tab' || e.key === 'Enter' || e.key === 'Escape') ) {
  3299. return;
  3300. }
  3301. switch ( e.key ) {
  3302. case 'shiftKey':
  3303. switch(true) {
  3304. case $('body').hasClass('has_warning') || $('body').hasClass('has_help'):
  3305. if (e.key !== 'Enter' && e.key !== 'Tab') { e.preventDefault(); return false; }
  3306. break;
  3307. }
  3308. break;
  3309. case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight':
  3310. showWarning( 'arrowNavigation',e ); // arrow navigation
  3311. break;
  3312. case ' ': // space
  3313. if ( $('#content_pane').hasClass('has_audio') || $('#content_pane').attr('data-content') === 'has_video' ) { // Play/pause media (space bar)
  3314. e.preventDefault(); e.stopPropagation(); playPauseMedia();
  3315. } else if ( window.top !== window.self && $('tr.audio.playing').length === 1 ) {
  3316. e.preventDefault(); e.stopPropagation(); sendMessage('top','playPauseMedia');
  3317. } else {
  3318. alphaNav(e);
  3319. }
  3320. break;
  3321. case 'Enter': // Open directories (or ignore)
  3322. switch(true) {
  3323. case $('body').hasClass('has_warning') || $('body').hasClass('has_help'):
  3324. e.preventDefault();
  3325. $('button.focus, button:focus').click(); // click focused warning button in #top or #iframe
  3326. break;
  3327. case window.self !== window.top: // if iframe...
  3328. switch(true) {
  3329. case $('body').hasClass('has_menu'):
  3330. sendMessage('top','clickMenu'); // close main menu
  3331. break;
  3332. case $('tr.selected').hasClass('dir') && cmdKey(e):
  3333. case $('tr.selected').hasClass('file') && cmdKey(e): // open iframe dir or file
  3334. $('tr.selected').find('a').trigger('dblclick');
  3335. break;
  3336. case !$('.audio.selected').hasClass('playing'):
  3337. $('.audio.selected').find('a').trigger('dblclick');
  3338. break;
  3339. case $('.audio.playing').length === 1 && !$('.selected').hasClass('playing'): // play/pause media
  3340. e.preventDefault(); e.stopPropagation(); playPauseMedia();
  3341. break;
  3342. }
  3343. break;
  3344. case $('body').hasClass('has_menu'):
  3345. e.preventDefault(); clickMenu(); sendMessage('iframe','close_menu'); // click selected menu item
  3346. break;
  3347. case $selected.hasClass('app') && $settings.apps_as_dirs === false: // don't open app folders
  3348. break;
  3349. default:
  3350. switch(true) {
  3351. case $selected.hasClass('.disabled'):
  3352. case $content_pane.attr('data-content') === 'has_text_editor':
  3353. break;
  3354. case $selected.hasClass('media') && !$selected.hasClass('playing'): // load selected media file
  3355. $('.media.selected a').click();
  3356. break;
  3357. case $selected.hasClass('media'): // else play/pause playing media
  3358. e.preventDefault(); e.stopPropagation(); playPauseMedia();
  3359. break;
  3360. case $selected.hasClass('dir'): // else open dir
  3361. $selected.find('a').trigger('dblclick');
  3362. break;
  3363. default:
  3364. $selected.click();
  3365. }
  3366. }
  3367. break;
  3368. // Alphabetical navigation
  3369. 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':
  3370. 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':
  3371. 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 'π':
  3372. 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 '≠':
  3373. case '⁄': case '€': case '‹': case '›': case 'fl': case '‡': case '°': case '·': case '‚': case '±': case '¯': case '˘': case '¿':
  3374. case 'ı': case '': case '´': case '˝': case 'ˆ': case '': case '˜': case '‰': case 'ˇ': case '¨': case '◊': case '„': case '˛': case '¸':
  3375. // 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 'ϖ':
  3376. // 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 'ϒ':
  3377. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) { alphaNav(e); }
  3378. break;
  3379. case 'd': // Cmd/Ctrl + D: Toggle Details
  3380. if ( cmdKey(e) && !$body.hasClass('has_warning') ) { e.preventDefault(); $('#show_details').click(); } else { alphaNav(e); }
  3381. break;
  3382. case 'e': // Cmd/Ctrl + E: Toggle Main Menu or Text Editor
  3383. switch(true) {
  3384. case $body.hasClass('has_warning'): break;
  3385. case cmdShiftKey(e): // toggle text editor
  3386. e.preventDefault();
  3387. if ( window.self !== window.top ) { sendMessage('top','toggle_text_editor'); } else { $('#text_editor_row').find('a').click(); }
  3388. $('body').addClass('faded');
  3389. break;
  3390. case cmdKey(e): // toggle main menu
  3391. e.preventDefault();
  3392. if ( window.self !== window.top ) { sendMessage('top','toggle_menu'); } else { $('#menu_container').click(); }
  3393. break;
  3394. default:
  3395. alphaNav(e);
  3396. }
  3397. break;
  3398. case 'g': // Cmd/Ctrl + G: Show image Grid
  3399. if ( cmdKey(e) && arr1InArr2(['has_images','has_fonts'],getClassList('top')) ) { e.preventDefault(); $('#grid_btn').click(); } else { alphaNav(e); }
  3400. break;
  3401. case 'i': // Cmd/Ctrl + I: Toggle Invisibles
  3402. if ( cmdKey(e) ) { e.preventDefault(); $('#show_invisibles_container').find('input').click(); } else { alphaNav(e); }
  3403. break;
  3404. case 'o': // Cmd/Ctrl + Shift + O: Open selected item in new window
  3405. if ( cmdShiftKey(e) ) { window.open( thisLink($('#tbody').find('.selected')) ); } else { alphaNav(e); }
  3406. break;
  3407. case 'r': // Cmd/Ctrl + Shift + R: Refresh
  3408. switch(true) {
  3409. case cmdKey(e) && window.top !== window.self:
  3410. e.preventDefault(); sendMessage('top','reload'); // send reload message to top
  3411. break;
  3412. case cmdKey(e):
  3413. e.preventDefault(); $('#reload_btn').click(); // click reload/reset button
  3414. break;
  3415. default:
  3416. alphaNav(e);
  3417. }
  3418. break;
  3419. 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
  3420. switch(true) {
  3421. case cmdKey(e):
  3422. switch(true) {
  3423. case window.top !== window.self:
  3424. e.preventDefault();
  3425. sendMessage('top','close'); // send close message to top
  3426. break;
  3427. case $content_pane.hasClass('has_audio'):
  3428. case $content_pane.attr('data-content') !== undefined:
  3429. e.preventDefault();
  3430. $('#close_btn').click(); // click close button
  3431. break;
  3432. default:
  3433. return true; // else close window (or normal behavior)
  3434. }
  3435. break;
  3436. default:
  3437. alphaNav(e);
  3438. }
  3439. break;
  3440. case '=': // equals sign
  3441. switch(true) {
  3442. case cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test($content_pane.attr('data-content')):
  3443. e.preventDefault(); $('#increase').click();
  3444. break;
  3445. case cmdKey: // allow normal Cmd + behavior
  3446. return;
  3447. default:
  3448. alphaNav(e);
  3449. break;
  3450. }
  3451. break;
  3452. case '-': // hyphen
  3453. switch(true) {
  3454. case cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test($content_pane.attr('data-content')):
  3455. e.preventDefault(); $('#decrease').click();
  3456. break;
  3457. case cmdKey: // allow normal Cmd - behavior
  3458. return;
  3459. default:
  3460. alphaNav(e);
  3461. break;
  3462. }
  3463. break;
  3464. case '\\': // \ backslash
  3465. switch(true) {
  3466. case cmdShiftKey(e): // Cmd Shift + \ : toggle split
  3467. switch(true) {
  3468. case window.top === window.self && $content_pane.attr('data-content') === 'has_iframe':
  3469. sendMessage('iframe','toggle_split'); // send toggle split message to top
  3470. break;
  3471. case $('#toggle_split').height() > 0:
  3472. $('#toggle_split').click(); // click toggle split
  3473. break;
  3474. }
  3475. break;
  3476. case cmdKey(e): // Cmd + \ : toggle sidebar
  3477. if ( window.top !== window.self ) { sendMessage('top','toggle_sidebar'); } else { $('#toggle_sidebar').click(); }
  3478. break;
  3479. }
  3480. break;
  3481. case 'Tab':
  3482. if ( document.activeElement.hasAttribute('contenteditable') ) { return; }
  3483. e.preventDefault();
  3484. switch(true) {
  3485. case $('body').hasClass('has_warning'):
  3486. tabWarningButtons(e);
  3487. break;
  3488. case window.top === window.self: // If iframe is not focused
  3489. if ( $('body').hasClass('focus_content') ) { focusSidebar(); } else { focusContent(); }
  3490. break;
  3491. case window.top !== window.self: // If iframe is focused
  3492. if ( document.activeElement.tagName.toLowerCase() === 'textarea' ) { return; } else { sendMessage('top','tab'); }
  3493. if ( !$('#toolbar').length ) { // if not text editor (i.e., is a dir), focus sidebar.
  3494. $('tr.selected').toggleClass('selected dimmed');
  3495. } else {
  3496. focusText();
  3497. }
  3498. break;
  3499. }
  3500. break;
  3501. case 'Escape':
  3502. window.getSelection().removeAllRanges();
  3503. $('#content_playlist, #content_audio_playlist').removeClass('has_content');
  3504. $('#warning_btn_cancel,#close_help').click();
  3505. if ( window.top !== window.self ) { sendMessage('top','escape'); } else { closeMenus(); focusSidebar(); }
  3506. break;
  3507. case '.':
  3508. switch(true) {
  3509. case cmdKey(e):
  3510. if ( $('body').hasClass('has_warning') ) {
  3511. e.preventDefault();
  3512. $('#warning_btn_cancel,#close_help').click(); // click cancel buttons
  3513. }
  3514. break;
  3515. default:
  3516. break;
  3517. }
  3518. } // end switch
  3519. });
  3520. // ***** END KEYBOARD EVENTS ***** //
  3521.  
  3522. // ***** GRID SETUP ***** //
  3523. // Create Font Grid Items
  3524. function fontGridItems() {
  3525. let $font_grid_items_arr = [], $font_files = $dir_list_body.find('.font'), fontGridStyles = $font_grid_styles.sheet;
  3526. for ( let i = $font_files.length; i--; ) {
  3527. let newGridItem = showFont( $font_files[i], true, fontGridStyles );
  3528. $font_grid_items_arr.unshift( newGridItem );
  3529. }
  3530. $font_grid_items_arr[$font_grid_items_arr.length - 1].addClass('border_bottom_x'); // add bottom border to last
  3531. return $font_grid_items_arr;
  3532. }
  3533. // Create Image Grid Items
  3534. function imageGridItems() {
  3535. let $image_grid_items_arr = [], $image_files = $dir_list_body.find('.image:not(.ignore)'), classes = 'image_grid_item border_right_x border_bottom_x';
  3536. for ( let i = $image_files.length; i--; ) {
  3537. const id = $image_files.eq(i).attr('id');
  3538. const $this_link = thisLink($image_files[i]);
  3539. const exts = $row_types.image.filter( ext => $.inArray(ext, $row_settings.ignore) == -1 ); // decide which image files can be displayed
  3540. const $title_name = $this_link.slice($this_link.lastIndexOf('/') + 1);
  3541. if ( $.inArray( thisExt($image_files[i]), exts ) > -1 ) { // if this row file ext is in the image extension array
  3542. 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>';
  3543. $image_grid_items_arr.unshift( item );
  3544. }
  3545. }
  3546. return $image_grid_items_arr;
  3547. }
  3548. // Make Grids
  3549. function makeGrids(id) {
  3550. closeGrid(); // remove previous grid items
  3551. $content_pane.removeClass('has_hidden_grid has_image_grid has_font_grid'); // reset content_pane grid classes
  3552. switch(true) {
  3553. case id === 'show_font_grid' || !$body.hasClass('has_images'): // only show font grid
  3554. $content_pane.addClass('has_font_grid'); $content_grid.append( fontGridItems() );
  3555. break;
  3556. case id === 'show_image_grid' || !$body.hasClass('has_fonts'): // only show image grid
  3557. $content_pane.addClass('has_image_grid'); $content_grid.append( imageGridItems() );
  3558. break;
  3559. default: // show grid of both images and fonts
  3560. $content_grid.append( imageGridItems(), fontGridItems() );
  3561. }
  3562. }
  3563. // Click grid button: make grid and show grid
  3564. $('#sidebar_header').on('click', '#grid_btn, #show_font_grid, #show_image_grid', function(e) {
  3565. e.stopPropagation(); showContent($(this).attr('id'));
  3566. });
  3567.  
  3568. // ***** SCALE PREVIEWED IMAGES & FONTS ***** //
  3569. // Scale Fonts
  3570. function scaleFonts(e, incr, id) {
  3571. const fontSize = parseInt(getComputedStyle(document.body).fontSize); // pts/em
  3572. const getFontSize = function(el) { return parseFloat(el.css('font-size')); };
  3573. if ( id === 'decrease' ) { incr = 1/incr; }
  3574. if ( $content_pane.attr('data-content') === 'has_grid' ) {
  3575. $content_grid.css({'font-size':( getFontSize($content_grid)/fontSize * incr ) +'em'});
  3576. return;
  3577. }
  3578. if ( $content_pane.attr('data-content') === 'has_font' ) {
  3579. $content_font.css({'font-size':( getFontSize($content_font)/fontSize * incr ) +'em'});
  3580. return;
  3581. }
  3582. scrollThis('content_container','content_font');
  3583. }
  3584. // Scale Glyphs
  3585. function scaleGlyphs(e, incr, id) {
  3586. if ( id === 'decrease' ) { incr = 1/incr; }
  3587. let scale = ( $('#glyph_viewer').attr('data-scale') === undefined ? incr : incr * $('#glyph_viewer').attr('data-scale') );
  3588. if ( scale >= 1 ) {
  3589. $('#glyph_viewer').css({'width': scale * 100 +'%','height': scale * 100 +'%'});
  3590. document.getElementById('content_font').scrollLeft = -( Math.round( $('#font_viewer').outerWidth(true) - $('#glyph_viewer').width() ) / 2 );
  3591. document.getElementById('content_font').scrollTop = -( Math.round( $('#font_viewer').outerHeight(true) - $('#glyph_viewer').height() ) / 2 );
  3592. }
  3593. $('#glyph_viewer').css({'background-size': scale * 100 +'%'}).attr('data-scale',scale.toFixed(2));
  3594. }
  3595. // Scale Image Grid items
  3596. function scaleGridImages(incr,id) {
  3597. if ( id === 'decrease' ) { incr = 1/incr; }
  3598. const $imageGridItem = $('.image_grid_item img');
  3599. let $image_grid_item_width = Number.parseFloat( $imageGridItem.width(),10) * incr;
  3600. let $image_grid_item_height = Number.parseFloat( $imageGridItem.height(),10) * incr;
  3601. let $image_grid_item_maxwidth = Number.parseFloat( $imageGridItem.css('maxWidth'),10) * incr;
  3602. let $image_grid_item_maxheight = Number.parseFloat( $imageGridItem.css('maxHeight'),10) * incr;
  3603. // prevent reducing grid image size on first scale click:
  3604. if ( $image_grid_item_width < $image_grid_item_maxwidth ) { $image_grid_item_width = $image_grid_item_maxwidth; }
  3605. if ( $image_grid_item_height < $image_grid_item_maxheight ) { $image_grid_item_height = $image_grid_item_maxheight; }
  3606. // set grid properties
  3607. $content_grid.css({'grid-template-columns':'repeat(auto-fill, minmax('+ ($image_grid_item_width +16) +'px, auto ) )'});
  3608. $content_grid.find('img').css({'max-width':( $image_grid_item_width ) +'px', 'max-height':( $image_grid_item_height ) +'px'});
  3609. return;
  3610. }
  3611. // Zoom Images on click
  3612. function scaleImages(e,incr,id) {
  3613. const $this_image = ( $('#iframe_body > img').length === 1 ? $('#iframe_body > img') : $content_image.find('img') );
  3614. const $this_link = $this_image.attr('src');
  3615. const $content_container = ( $('#iframe_body > img').length === 1 ? $('#iframe_body') : $('#content_container') );
  3616. let CC_width = function() { return Math.round($content_container.width()); };
  3617. let CC_height = function() { return Math.round($content_container.height()); };
  3618. const $this_width = Math.round($this_image.width());
  3619. const $this_height = Math.round($this_image.height());
  3620. const $iframe_delta = ( $('#iframe_body > img').length === 1 ? Number.parseInt($('#iframe_body').css('padding')) : 0 );
  3621. // scale grid images
  3622. if ( $content_pane.attr('data-content') === 'has_grid' ) {
  3623. scaleGridImages(incr,id);
  3624. } else { // scale single images
  3625. getDimensions( $this_link, function( width, height ) {
  3626. if ( incr !== undefined && id !== undefined ) { // scale images by increment
  3627. $content_pane.addClass('has_scaled_image').removeClass('has_zoom_image'); // remove zoom classes in case window resized after zoom
  3628. if ( id === 'increase' ) {// && $this_width < width && $this_height < height) {
  3629. $this_image.css({'width':$this_width * incr, 'height': $this_height * incr});
  3630. $this_image.css({'width':$this_width * incr, 'height': 'auto', 'max-width':'none', 'max-height':'none' });
  3631. }
  3632. if ( id === 'decrease' && ( $this_width >= 1 && $this_height >= 1 ) ) {
  3633. $this_image.css({'width':$this_width / incr, 'height': 'auto', 'max-width':'none', 'max-height':'none' });
  3634. }
  3635. // keep images centered when scaling
  3636. if ( Math.round($this_image.width()) >= CC_width() ) {
  3637. $content_image.scrollLeft( ( Math.round( ( $this_image.outerWidth(true) ) - CC_width() ) )/2 ) ;
  3638. }
  3639. if ( Math.round($this_image.height()) <= CC_height() ) {
  3640. $content_image.scrollTop( ( CC_height() - Math.round( $this_image.height() ) )/2 );
  3641. } else {
  3642. $content_image.scrollTop( ( Math.round($this_image.outerHeight(true)) - CC_height())/2 ) ;
  3643. }
  3644. } else { // else zoom single image on click
  3645. $this_image.removeAttr('style');
  3646. if ( width <= CC_width() && height <= CC_height() ) { // but don't zoom small images:
  3647. $content_pane.removeClass('has_zoom_image has_scaled_image');
  3648. return;
  3649. } // otherwise, zoom image:
  3650. const $CC_offset = $content_container.offset();
  3651. const $img_offset = $this_image.offset();
  3652. // x,y coordinates of zoom click as percentage of image width/height
  3653. const percentX = (e.pageX - $img_offset.left)/$this_width;
  3654. const percentY = (e.pageY - $img_offset.top)/$this_height;
  3655. // calculate scroll by pixel coordinates of full-size image - click coordinates and content_container offsets
  3656. const scrollX = (width * percentX) - e.pageX + $CC_offset.left - ($iframe_delta * width / $this_width);
  3657. const scrollY = (height * percentY) - e.pageY + $CC_offset.top - ($iframe_delta * height / $this_height);
  3658.  
  3659. $content_pane.removeClass('has_scaled_image').toggleClass('has_zoom_image' );
  3660. $content_image.scrollLeft( scrollX );
  3661. $content_image.scrollTop( scrollY );
  3662. }
  3663. });
  3664. setImageDimensions();
  3665. }
  3666. }
  3667. // Zoom single image on click
  3668. $content_image.on('click','img', function(e) { scaleImages(e); focusContent('content_image'); });
  3669. // Scale Fonts and Images
  3670. function scalePreviewItems(e,id) { // combine scaling into one function
  3671. if ( $content_pane.attr('data-content') === 'has_glyph' ) { scaleGlyphs(e, 1.125, id); return; }
  3672. scaleImages(e, 1.125, id );
  3673. scaleFonts(e, 1.125, id );
  3674. }
  3675. // Scale Buttons
  3676. $('#scale').on('click','span', function(e) {
  3677. e.preventDefault(); e.stopPropagation();
  3678. scalePreviewItems(e, $(this).attr('id') );
  3679. $('#reload_btn').addClass('reset');
  3680. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  3681. });
  3682. // ***** END SCALE PREVIEW ITEMS ***** //
  3683.  
  3684. // ***** AUDIO CONTENT ***** //
  3685. // Update Playlist
  3686. function updatePlaylist() {
  3687. let playlist = [];
  3688. $dir_list_body.find('.audio').not('.unchecked').not('.disabled').each(function() { playlist.push( thisID( $(this) ) ); });
  3689. return playlist;
  3690. }
  3691. // Randomize Shuffle List
  3692. function shuffleArray(array) {
  3693. for ( let i = array.length - 1; i > 0; i-- ) {
  3694. const j = Math.floor(Math.random() * (i + 1));
  3695. [array[i], array[j]] = [array[j], array[i]];
  3696. }
  3697. return array;
  3698. }
  3699. // Attach Shuffle List data to $audio_player
  3700. function updateShuffleList(id) {
  3701. let shuffleList;
  3702. switch(true) {
  3703. case !$body.hasClass('shuffle_audio'):
  3704. break;
  3705. case id !== undefined: // don't include .playing and .unchecked track in shufflelist
  3706. shuffleList = $audio_player.data('shufflelist');
  3707. switch(true) {
  3708. case $(document.getElementById(id)).hasClass('unchecked') || $(document.getElementById(id)).hasClass('playing'):
  3709. shuffleList.splice(shuffleList.indexOf(id), 1); $audio_player.data('shufflelist',shuffleList); break;
  3710. default:
  3711. shuffleList.push(id); shuffleList = shuffleArray( shuffleList );
  3712. }
  3713. break;
  3714. default:
  3715. shuffleList = shuffleArray( updatePlaylist() );
  3716. $audio_player.data('shufflelist',shuffleList);
  3717. }
  3718. }
  3719. // Check/Uncheck single Audio/Video Files and update shufflelist
  3720. function toggleChecked(el) { $(el).blur(); thisRow(el).toggleClass('unchecked'); updateShuffleList(thisRow(el).attr('id')); }
  3721. $('#tbody').on('click','.media input', function(e) { e.stopPropagation(); toggleChecked($(this)); });
  3722. // Check/Uncheck all Audio/Video Files and update shufflelist
  3723. function toggleAllChecked() { $dir_list_body.find('> tr').find('input').trigger('click'); updateShuffleList(); }
  3724. // Is Playing; returns true if all conditions are true
  3725. function isPlaying(el) { return (el !== undefined && el.get(0).currentTime > 0 && !el.get(0).paused && !el.get(0).ended); }
  3726. // Play Media
  3727. function playMedia(task) {
  3728. if ( $content_pane.hasClass('has_audio') ) { $audio_player.trigger(task); } else { $content_video.trigger(task); }
  3729. }
  3730. // Skip media tracks +/-10/30 seconds
  3731. function mediaSkip(e,args) {
  3732. let factor, skip;
  3733. switch(true) {
  3734. case e !== undefined: // from top
  3735. factor = ( e.key === 'ArrowLeft' ? -1 : 1 ); // forward or backward?
  3736. skip = ( e.altKey && e.shiftKey ? 30 : e.altKey ? 10 : null ); // 30s or 10s?
  3737. break;
  3738. case args !== undefined: // from iframe
  3739. factor = ( args[0] === 'ArrowLeft' ? -1 : 1 ); // forward or backward?
  3740. skip = args[1]; // 30s or 10s?
  3741. break;
  3742. }
  3743. const $player = ( $('#content_pane').hasClass('has_audio') ? $audio_player : $content_video ); // audio or video?
  3744. const time = $player.prop('currentTime'); // current time
  3745. $player.prop('currentTime', time + factor*(skip)); // set time
  3746. }
  3747.  
  3748. // Play/Pause Audio/Video
  3749. function playPauseMedia() {
  3750. let $player = $('#audio');
  3751. if ( $content_pane.attr('data-content') === 'has_video' ) { $player = $('#content_video'); }
  3752. if ( isPlaying( $player ) ) { $player.trigger('pause'); } else { $player.trigger('play'); }
  3753. }
  3754. // Toggle Audio Playback Options (shuffle, loop)
  3755. function audioPlaybackOptions(id) {
  3756. if ( id === 'shuffle' ) {
  3757. $body.toggleClass('shuffle_audio');
  3758. updateShuffleList();
  3759. if ( $body.hasClass('shuffle_audio') && $('.playing').length === 0 ) { playPrevNextTrack('ArrowRight'); } else { /* do nothing: i.e., allow current track to continue playing */ }
  3760. } else {
  3761. $body.toggleClass('loop_audio');
  3762. document.getElementById('audio').toggleAttribute('loop');
  3763. }
  3764. }
  3765. // click loop or shuffle audio options
  3766. $('#audio_options').on('click','input', function() { audioPlaybackOptions( $(this).attr('id') ); });
  3767. // Initialize Media: play next track on ended and scroll to playing item
  3768. function initMedia() { $('#audio, #content_video').on('ended', function() {
  3769. $('#next_track').click();
  3770. scrollThis('tbody','playing'); });
  3771. }
  3772. // ***** END AUDIO PLAYBACK ***** //
  3773.  
  3774. // ***** IFRAME SETUP ***** //
  3775. function setUpIframeUI() { // setup iframe directory UI or iframe text editor
  3776. $('body').css({'background-color':'rgb(241, 243, 244)'}); // add iframe body id
  3777. $('video').css({'width':'100%'});
  3778. $('body').attr('id','iframe_body'); // add iframe body id
  3779. const $textFiles = $row_types.markdown.concat($row_types.text, $row_types.code); // define which files are editable
  3780. switch(true) {
  3781. case window.location.pathname.endsWith('/'): // set up iframe directory
  3782. setUpIframeDirUI();
  3783. break;
  3784. case $textFiles.includes( window.location.pathname.slice( window.location.pathname.lastIndexOf('.') + 1 ) ): // else set up iframe text editor
  3785. setUpTextEditorUI();
  3786. break;
  3787. }
  3788. }
  3789. // IFRAME DIRECTORY Prep
  3790. function setUpIframeDirUI() {
  3791. let link = decodeURIComponentSafe(window.location.href);
  3792. let parentLink = link.split('/').slice(0,-2).join('/') +'/';//+ window.location.search;
  3793. let queryPrefs = window.location.search;
  3794. queryPrefs = queryPrefs.slice(1).split('&');
  3795. queryPrefs = queryPrefs.map(x => x.split('='));
  3796. let prefs = Object.fromEntries(queryPrefs); // convert query array of arrays to object
  3797. let sortPref = prefs.sort_by; // sort determined by parent's current sorting pref
  3798. let sortDirection = ( getQuery('sort_direction') === 'up' ? -1 : 1 ); // get sort direction
  3799. let viewSourcePref = prefs.view_directory_source;
  3800. let $iframeBody = $('#iframe_body'), $iframeHead = $('head');
  3801. let bodyClasses = []; // make array of classes to add all at once
  3802. if ( viewSourcePref === 'true' || prefs.is_error === 'true' ) { // show raw directory index if error or viewing directory source
  3803. return;
  3804. } else { // else set up iframe directory
  3805. if ( prefs.alternate_background === 'true' ) { bodyClasses.push('alternate_background'); }
  3806. if ( prefs.ignore_ignored_files === 'true' ) { bodyClasses.push('ignore_ignored_files'); }
  3807. if ( prefs.show_invisibles === 'true' ) { bodyClasses.push('show_invisibles'); }
  3808. if ( prefs.show_numbers === 'true' ) { bodyClasses.push('show_numbers'); }
  3809. if ( prefs.theme === 'dark' ) { bodyClasses.push('theme_dark'); } else { bodyClasses.push('theme_light'); }
  3810. if ( prefs.use_custom_icons === 'true' ) { bodyClasses.push('use_custom_icons'); }
  3811. if ( getBrowser() === 'firefox' ) { bodyClasses.push('is_gecko'); }
  3812. bodyClasses.push('sort_by_'+ sortPref);
  3813. $iframeBody.data('sort_direction',sortDirection); // initial directory sort
  3814. $iframeHead.find('style').remove(); // remove any existing directory index styles
  3815. $iframeHead.append('<style id="iframe_dir_styles">'+ $iframe_dir_styles +'</style><style id="color_and_background_styles">'+ $color_and_background_styles +'</style>');
  3816. let iFrameTable = ContentIframeDirEls(parentLink); // get iframe directory UI elements
  3817. const preppedIndex = makeNewIndex($iframeBody,sortPref); // prepare directory index
  3818. $iframeBody.removeAttr('style').addClass( bodyClasses.join(' ') ).empty().append(iFrameTable).find('#tbody').append(preppedIndex); // append UI and prepped index
  3819. }
  3820. }
  3821. // IFRAME Directory sorting
  3822. function iframeSorting(el) {
  3823. const $iframe_dir_list_rows = $('#iframe_body').find('#tbody').find('tr');
  3824. const id = el.attr('id'); // = new sort class
  3825. let current_sort = document.getElementById('iframe_body').classList.value.match(/sort_by_\w+/)[0];
  3826. if ( $('#iframe_body').data('sorting') !== id ) { // if clicking sorting item for the first time
  3827. $('#iframe_body').data('sorting',id);
  3828. $('#iframe_body').data('sorting',id).data('sort_direction', 1 );
  3829. el.removeClass('up');
  3830. } else { // clicking the same sorting item again -- reverse sort order
  3831. $('#iframe_body').data('sort_direction', $('#iframe_body').data('sort_direction') * -1 );
  3832. el.toggleClass('up');
  3833. }
  3834. const sort_direction = $('#iframe_body').data('sort_direction');
  3835. const $sorted_index = sortDirList( $iframe_dir_list_rows, id, sort_direction );
  3836. $('#iframe_body').removeClass(current_sort).addClass(id).find('#tbody').empty().append($sorted_index);
  3837. }
  3838. $('#iframe_body').on('click','.sorting', function(e) { e.preventDefault(); iframeSorting($(this)); });
  3839. // IFRAME Click to select item
  3840. $('#iframe_body').on('click', function() { sendMessage('top','iframe_click'); });
  3841. // IFRAME Links
  3842. $('#iframe_body').on('click','a', function(e) {
  3843. let link = $(this).attr('href'), url;
  3844. if ( link.startsWith('#') ) { return true; } else { e.preventDefault(); url = newURL(link); }
  3845. switch(true) {
  3846. case $(this).parents('#tbody').length:
  3847. case url === undefined:
  3848. case link.startsWith('#'):
  3849. break;
  3850. case $(this).parent().attr('id') === 'parent': // open parent directory in iframe
  3851. sendMessage('top','top_show_iframe_dir','',link); // adds data:iframe_link to $content_iframe
  3852. break;
  3853. case url.protocol !== 'file:' && window.location.protocol === 'file:': // open remote link from local file in new tab/window
  3854. window.open(link,'_blank');
  3855. break;
  3856. case url.protocol === 'file:' && window.location.protocol === 'file:': // open local links on local files in iframe
  3857. case url.protocol === 'about:': // document #links
  3858. case RegExp(url.hostname).test(window.location.hostname): // same origin links (might not include TLD) (just covering bases here)
  3859. case RegExp(window.location.hostname).test(url.hostname): // same origin links (might not include TLD) (just covering bases here)
  3860. // window.location = link;
  3861. if ( getItemKind( getItemExt(link) ) === 'dir' ) {
  3862. sendMessage('top','top_show_iframe_dir','',link);
  3863. } else {
  3864. sendMessage('top','top_show_iframe_file','',[link, getItemKind( getItemExt(link) ) ]);
  3865. }
  3866. break;
  3867. default:
  3868. window.open(link,'_blank');
  3869. break; // else open external document links in new tab
  3870. }
  3871. });
  3872. // IFRAME Click item to add selected class or play/pause media
  3873. $('#iframe_body').on('click','#tbody tr', function(e) {
  3874. e.preventDefault();
  3875. $(this).addClass('selected').siblings('tr').removeClass('dimmed selected');
  3876. if ( $(this).hasClass('audio') && $(this).hasClass('playing') ) { sendMessage('top','iframe_play_pause_media'); } else { sendMessage('top','iframe_click'); } // play/pause media or send message
  3877. });
  3878. // IFRAME Doubleclick for directory and file navigation
  3879. $('#iframe_body').on('dblclick','#tbody tr a', function(e) {
  3880. e.preventDefault(); e.stopPropagation();
  3881. let link = $(this).attr('href');
  3882. if ( window.location.protocol === 'file:' ) { // send full path for non-local files
  3883. link = 'file://'+ link;
  3884. } else {
  3885. link = window.location.protocol +'//'+ window.location.hostname + window.location.pathname + link;
  3886. }
  3887. let kind = $(this).closest('tr').attr('data-kind');
  3888. if ( kind === 'dir' ) {
  3889. sendMessage('top','top_show_iframe_dir','',link); // adds current navigated iframe directory link data:iframe_link to $content_iframe
  3890. } else { // display audio, font, image, pdf, video, and other files in corresponding content container
  3891. if ( kind === 'audio' ) { $(this).closest('tr').addClass('playing selected').siblings('.audio').removeClass('playing selected'); }
  3892. let args = [link,kind];
  3893. sendMessage('top','top_show_iframe_file','',args); // tell #top what to play
  3894. }
  3895. });
  3896. // Open IFRAME directory in sidebar
  3897. function openDirInSidebar() {
  3898. // let link = decodeURIComponentSafe(window.location.href.toString());
  3899. let link = decodeURIComponentSafe($('#open_in_sidebar').find('a').attr('href'));
  3900. sendMessage('top','top_open_in_sidebar','',link);
  3901. }
  3902. $('#open_in_sidebar').on('click',function(e) { e.preventDefault(); openDirInSidebar(); });
  3903.  
  3904. //***** TEXT EDITING PANE *****//
  3905. function setUpTextEditorUI() {
  3906. let sourceText, bodyClasses = [];
  3907. if ( !$('body').hasClass('has_text_editor_UI') ) { // add classes, styles, and scripts; only add once
  3908. $('head').append('<style id="text_editor_styles">'+ $text_editor_styles +'</style>');
  3909. $('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>');
  3910. $('#title span').empty().text('Text Editor');
  3911. }
  3912. switch(true) { // get source text and append UI elements
  3913. case window.self === window.top: // top level text editor
  3914. if ( !$('body').hasClass('has_text_editor_UI') ) {
  3915. $('body').addClass('has_text_editor_UI');
  3916. $('#content_text').append( TextEditingUIEls() );
  3917. if ( getQuery('sync_scroll') === 'true' ) { $('#sync_scroll input').prop({checked:true}); }
  3918. focusText();
  3919. }
  3920. break;
  3921. case window.self !== window.top: // iframe text editing UI
  3922. $('head').prepend('<meta charset="utf-8" /><meta http-equiv="Content-Type" content="text/plain; charset="utf-8">');
  3923. sourceText = decodeURIComponentSafe($('body').find('> pre').text()); // get source text and decode Unicode chars.
  3924. bodyClasses.push('has_text_editor_UI');
  3925. if ( getQuery('disable_text_editing') === 'true' ) {
  3926. $body.removeClass('split_view source_text');
  3927. bodyClasses.push('text_editing_disabled preview_text');
  3928. } else {
  3929. if ( getQuery('split_view') === 'true' ) { bodyClasses.push('split_view'); }
  3930. if ( getQuery('default_text_view') === 'preview' ) { bodyClasses.push('preview_text'); } else { bodyClasses.push('source_text'); }
  3931. }
  3932. if ( getQuery('editor_theme') !== '') { bodyClasses.push('editor_theme_'+ getQuery('editor_theme')); } else { bodyClasses.push('editor_theme_'+ getQuery('theme')); }
  3933. // add text editor ui and warnings
  3934. $('head').append('<style id="warning_styles">'+ $warning_styles +'</style>');
  3935. $('body').addClass( bodyClasses.join(' ') ).empty().append('<div id="content_text" class="background_color_DD_33">'+ TextEditingUIEls() +' </div><div id="warnings_container" class="">'+ Warnings() +'</div>');
  3936. $('#text_source').val(sourceText); // set the source text value
  3937. if ( getQuery('sync_scroll') === 'true' ) { $('#sync_scroll input').prop({checked:true}); }
  3938. break;
  3939. }
  3940. TextEditing(); // call text editing functions
  3941. }
  3942. // setup and show top level text editor
  3943. $('#text_editor, #text_editor_row').on('click', function(e) { e.preventDefault(); showContent( $(this).attr('id') ); });
  3944.  
  3945. // Main Text Editing Function
  3946. function TextEditing() {
  3947. const $toolbar = $('#toolbar');
  3948. const $source = $('#text_source');
  3949. const $preview = $('#text_preview');
  3950. const $html = $('#html_preview');
  3951. const $MDhandle = $('#text_editing_handle');
  3952. // Toolbar button functions
  3953. $toolbar.on('click','li,span', function(e) { e.stopPropagation(); MDtoolBarFunctions($(this).attr('id')); });
  3954. $toolbar.on('mousedown', function(e) { e.preventDefault(); }); // prevent textarea from losing focus when clicking sidebar
  3955. // Resize
  3956. $MDhandle.on('mousedown', function(e) { e.stopPropagation(); MDresizeSplit($MDhandle,$source); });
  3957. $(window).on('resize', function() { $source.add($preview).add($MDhandle).attr('style',''); }); // reset split to 50/50 on window resize;
  3958. // Click labels to toggle checkboxes
  3959. $preview.add($toolbar).on('click','label', function(e) { e.stopPropagation(); $(this).siblings('input').click(); });
  3960. // Sync scroll
  3961. $source.on('scroll', function() { MDsyncScroll(this); });
  3962. $preview.on('scroll', function() { MDsyncScroll(this); });
  3963. $html.on('scroll', function() { MDsyncScroll(this); });
  3964. // Generate Preview
  3965. const $source_text = ( $source.length === 0 ? '' : $source.val() );
  3966. MDmarkdown( $source_text, $preview );
  3967. // Live preview update, and set edited classes for unsaved warning
  3968. $source.on('input', function() { // only add class or send message once after editing
  3969. if ( !$('body').hasClass('edited') ) {
  3970. $('body').addClass('edited'); // add edited class
  3971. if ( window.top !== window.self ) { sendMessage('top','iframe_edited','',''); } // send edited message to top
  3972. }
  3973. MDlivePreview($source,$preview);
  3974. });
  3975. // Checklists
  3976. MDsetChecklistClass();
  3977. // Live checkboxes
  3978. $preview.on('click','.checklist input', function(e) { e.stopPropagation(); MDliveCheckBoxes($(this),$source,$preview); });
  3979. // Preview TOC click navigation
  3980. $preview.on('click','.table-of-contents a', function(e) { e.preventDefault(); MDtocClick($(this),$preview); });
  3981. // Click header uplinks
  3982. $preview.on('click','.uplink', function(e) { e.stopPropagation(); MDheaderClick($preview); });
  3983. }
  3984. ///// END MAIN MD FUNCTION
  3985.  
  3986. // MARKDOWN Functions
  3987. // Focus Text
  3988. function focusText() {
  3989. switch(true) {
  3990. case $('body').hasClass('split_view'):
  3991. case $('body').hasClass('source_text'):
  3992. $('#text_source').focus();
  3993. break;
  3994. case $('body').hasClass('has_html'):
  3995. $('#html_preview').focus();
  3996. break;
  3997. case $('body').hasClass('preview_text'):
  3998. $('#text_preview').focus();
  3999. break
  4000. }
  4001. }
  4002. // MD focus or blur text editor
  4003. $('#text_source').on('click',function(e) { e.preventDefault(); });
  4004. // Restore text selection on focus
  4005. $('#content_text').on('focus','#text_source',function() {
  4006. let selectionStart, selectionEnd;
  4007. if ( window.self === window.top ) {
  4008. selectionStart = $('#text_source').attr('data-selection_start');
  4009. selectionEnd = $('#text_source').attr('data-selection_end');
  4010. } else {
  4011. selectionStart = $('#text_source').attr('data-selection_start');
  4012. selectionEnd = $('#text_source').attr('data-selection_end');
  4013. }
  4014. if ( selectionStart !== undefined && selectionEnd !== undefined ) {
  4015. document.getElementById('text_source').setSelectionRange(selectionStart, selectionEnd); //, selectionDirection
  4016. }
  4017. });
  4018. // Save text range or cursor location
  4019. $('#content_text').on('blur','#text_source',function() {
  4020. let selectionStart = $(this).prop('selectionStart');
  4021. let selectionEnd = $(this).prop('selectionEnd');
  4022. if ( window.self === window.top ) {
  4023. $(this).attr('data-selection_start',selectionStart).attr('data-selection_end',selectionEnd);
  4024. } else {
  4025. sendMessage('top','save_text_selection','',[selectionStart,selectionEnd]);
  4026. }
  4027. });
  4028. // Select Textarea Content
  4029. function selectTextareaContent(id) {
  4030. let textarea = document.getElementById(id);
  4031. textarea.focus();
  4032. textarea.select();
  4033. textarea.scrollTop = 0;
  4034. }
  4035. // MD UI Buttons functions
  4036. function MDtoolBarFunctions(id) {
  4037. let $thisFileName;
  4038. let sourceEl = $('#text_source');
  4039. let previewEl = $('#text_preview');
  4040. if ( $('#content_pane').attr('data-content') === 'has_text_editor' ) { $thisFileName = 'untitled'; } else { $thisFileName = decodeURI(window.location.pathname.slice(window.location.pathname.lastIndexOf('/') + 1)); }
  4041. const $saveHTMLOpen = '<!DOCTYPE html><html><head><meta charset="utf-8" /><title></title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link><style></style><script></script></head><body lang="en" class="markdown-body">';
  4042. const $saveHTMLClose = '</body></html>';
  4043. switch (id) {
  4044. case 'toggle_theme': $('body').toggleClass('editor_theme_dark editor_theme_light'); sourceEl.focus(); break;
  4045. case 'toggle_split':
  4046. $('body').toggleClass('split_view').find('#text_source,#text_preview,#html_preview,#text_editing_handle').attr('style','');
  4047. focusText();
  4048. break;
  4049. case 'show_source':
  4050. $('body').toggleClass('split_view').removeClass('preview_text').addClass('source_text');
  4051. focusText();
  4052. break;
  4053. case 'show_preview':
  4054. $('body').toggleClass('split_view').removeClass('source_text').addClass('preview_text');
  4055. focusText();
  4056. break;
  4057. case 'show_html': // toggle html and preview
  4058. $('body').removeClass('source_text');
  4059. if ( $('body').hasClass('has_html') ) {
  4060. $('body').removeClass('has_html');
  4061. if ( document.getElementById('text_source') !== document.activeElement ) { $('#text_preview').focus(); }
  4062. } else {
  4063. let sourceHTML = previewEl.html().toString();
  4064. $('#html_preview').empty().text(sourceHTML);
  4065. $('body').addClass('has_html');
  4066. if ( document.getElementById('text_source') !== document.activeElement ) { $('#html_preview').focus(); }
  4067. }
  4068. break;
  4069. case 'clear_text':
  4070. if ( $('#text_source').val().length > 0 ) {
  4071. $('body').addClass('has_warning').find('#warnings_container').removeClass().addClass('clear');
  4072. focusButton('warning_btn_cancel');
  4073. } else {
  4074. focusText();
  4075. }
  4076. break;
  4077. case 'save_text': saveMD( $thisFileName +'.md', sourceEl.val() ); break;
  4078. case 'save_HTML': saveMD( $thisFileName +'.html', $saveHTMLOpen + MDprepHTML(previewEl.html()) + $saveHTMLClose ); break;
  4079. }
  4080. }
  4081. // MD Custom pre- and post-processing for text.
  4082. function MDaddHeaderIDs(match, p1, p2, p3) { return '<h'+ p1 +' id="'+ p3.toLowerCase().replace(/\s/g,'-') +'" ' + p2 +'>'+ p3; } // create header ids for TOC
  4083. function MDcustomPreProcess(src) { return src; } // we're not doing anything here just yet...
  4084. function MDcustomPostProcess(html) {
  4085. 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
  4086. .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
  4087. // .replace(/<li><p class="checklist">"/g,'<li class="checklist"><p>')
  4088. .replace(/^<h(\d)([^>]*)>([^<]+)/gm, MDaddHeaderIDs) // add header IDs;
  4089. .replace(/<\/h(\d)>/g,'<span class="uplink">&uarr;</span></h$1>');
  4090. return html;
  4091. }
  4092. //MD Render markdown from preprocessed source text
  4093. function MDmarkdown(sourceText,previewEl) {
  4094. const MDit = window.markdownit({linkify:false, typography:false, html:true})
  4095. .use(window.markdownitMultimdTable, {enableMultilineRows: true})
  4096. .use(window.markdownitSub)
  4097. .use(window.markdownitSup)
  4098. .use(window.markdownitFootnote)
  4099. .use(window.markdownitCentertext)
  4100. .use(window.markdownitDeflist)
  4101. .use(window.markdownitTocDoneRight)
  4102. ;
  4103. let MDpreview = MDit.render( MDcustomPreProcess( sourceText ) );
  4104. previewEl.html( MDcustomPostProcess( MDpreview ) ); // set previewed html
  4105. }
  4106. // MD Live preview, add edited warning
  4107. function MDlivePreview(sourceEl,previewEl) {
  4108. MDmarkdown( sourceEl.val(),previewEl );
  4109. MDsetChecklistClass();
  4110. let sourceHTML = previewEl.html().toString();
  4111. $('#html_preview').empty().text(sourceHTML);
  4112. }
  4113. // MD Live Checkboxes prep: find each instance of [ ] or [x] and replace text in index = to clicked checkbox in Preview.
  4114. function MDreplaceAt(str, replacement, position) { str = str.substring(0, position) + replacement + str.substring(position + replacement.length); return str; }
  4115. function MDreplaceNthSubStr(str,substr,replacement,index) {
  4116. let count = 0;
  4117. let found = substr.exec(str);
  4118. while ( found !== null ) {
  4119. if ( count === index ) { return MDreplaceAt(str, replacement, found.index ); } else { count++; found = substr.exec(str); }
  4120. }
  4121. }
  4122. // MD Live Checkboxes
  4123. function MDliveCheckBoxes(checkbox,sourceEl,previewEl) {
  4124. $('.checklist').removeClass('clicked');
  4125. checkbox.closest('p,li,dt,dd').addClass('clicked');
  4126. const thisIndex = previewEl.find('.checklist').index( $('.clicked') );
  4127. const srctext = sourceEl.val();
  4128. const substr = new RegExp(/\[\s*.\s*\]/g);
  4129. const replacement = ( checkbox.is(':checked') ? '[x]' : '[ ]' );
  4130. sourceEl.val( MDreplaceNthSubStr(srctext, substr, replacement, thisIndex) );
  4131. }
  4132. // MD Checkbox list class: Prevent checkbox lists from having list bullets
  4133. function MDsetChecklistClass() { $('input[type="checkbox"]').closest('ul').css({'list-style':'none','padding':'0'}); }
  4134. // MD Resize Split View
  4135. function MDresizeSplit(handle,sourceEl) {
  4136. let $sidebarWidth = $('#sidebar').outerWidth();
  4137. let $pageWidth = window.innerWidth;
  4138.  
  4139. $(document).on('mousemove',function(e) {
  4140. e.stopPropagation(); e.preventDefault();
  4141. let pageX = e.pageX;
  4142. if ( pageX > $sidebarWidth + 100 && pageX < $pageWidth - 100 ) { // min widths
  4143. $('#text_source').css({'width': pageX - $sidebarWidth + 'px'});
  4144. $('#html_preview').css({'width': $('#content_text').outerWidth() - sourceEl.outerWidth() + 'px'});
  4145. $('#text_preview').css({'width': $('#content_text').outerWidth() - sourceEl.outerWidth() + 'px'});
  4146. handle.css({'left': sourceEl.outerWidth() - 4 + 'px'});
  4147. }
  4148. });
  4149. handle.on('mouseup', function() { $(document).off('mousemove'); focusText(); });
  4150. }
  4151. // MD UI Sync Scroll
  4152. function MDpercentage(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)); }
  4153. function MDsyncScroll(el1) {
  4154. let el2; // scroll this element when scrolling el1
  4155. switch(true) {
  4156. case el1.getAttribute('id') === 'text_source':
  4157. el2 = ( $('body').hasClass('has_html') ? document.getElementById('html_preview') : document.getElementById('text_preview') );
  4158. break;
  4159. case el1.getAttribute('id') === 'text_preview': case el1.getAttribute('id') === 'html_preview':
  4160. el2 = document.getElementById('text_source');
  4161. break;
  4162. }
  4163. if ( document.querySelector('input[name="sync_scroll"').checked ) {
  4164. el2.scrollTo( 0, (MDpercentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
  4165. }
  4166. }
  4167. // click TOC anchors
  4168. function MDtocClick(el,previewEl) { let thisId = el.attr('href'); if ( thisId ) { previewEl.scrollTop( $(thisId).offset().top - 48 ); } }
  4169. // click Headers to return to TOC or top
  4170. function MDheaderClick(previewEl) {
  4171. if ( previewEl.find('.table-of-contents').length > 0 ) {
  4172. document.getElementsByClassName('table-of-contents')[0].scrollIntoView(true);
  4173. } else {
  4174. document.getElementById('preview').scroll(0,0);
  4175. }
  4176. }
  4177. // MD Clear text source
  4178. function clearText() {
  4179. if ( window.self !== window.top ) { sendMessage('top','iframe_edited'); }
  4180. $('body').removeClass('edited');
  4181. $('#text_preview,#html_preview').empty();
  4182. $('#text_source').val('').show().focus();
  4183. }
  4184. // MD SAVE SOURCE or HTML
  4185. function MDprepHTML(data) { data = data.replace(/<span\sclass="uplink">.<\/span>/g,''); return data; }
  4186. function saveMD(filename, data) {
  4187. saveFile(data,'text/plain',filename);
  4188. if ( window.top !== window.self ) { sendMessage('top','clear'); } // if iframe, send message to top
  4189. $('body,#text_source,#content_text').removeClass('edited');
  4190. }
  4191. // END TEXT EDITING
  4192.  
  4193. // MESSAGES
  4194. // Send a message to iframe or parent
  4195. function sendMessage(target,message,funcName,args) {
  4196. var messageObj = { 'messageContent': message, 'functionName': funcName, 'arguments': args };
  4197. if ( target === 'iframe' ) {
  4198. let contentIFrame = document.getElementById('content_iframe');
  4199. contentIFrame.contentWindow.postMessage( messageObj, '*' );
  4200. }
  4201. if ( target === 'top' ) {
  4202. window.parent.postMessage( messageObj, '*');
  4203. }
  4204. }
  4205. // Receive a message from iframe or parent, do appropriate action
  4206. function receiveMessage(e) {
  4207. if ( e.origin === 'null' || e.origin === $origin ) {
  4208. let $message = e.data.messageContent;
  4209. let funcName = e.data.functionName;
  4210. let args = e.data.arguments;
  4211. switch( $message ) {
  4212. case 'arrowNavigation':
  4213. arrowNavigation(args); // classname, key
  4214. break;
  4215. case 'toggle_sidebar': // toggle sidebar
  4216. $('#toggle_sidebar').click();
  4217. break;
  4218. case 'toggle_menu': // toggle menu
  4219. $('#menu_container').click();
  4220. break;
  4221. case 'close_menu': // close menu
  4222. case 'top_closed_menu':
  4223. $('#iframe_body').removeClass('has_menu');
  4224. break;
  4225. case 'top_has_menu':
  4226. $('#iframe_body').addClass('has_menu');
  4227. break;
  4228. case 'menu_navigation': // menu navigation from iframe
  4229. menuNavigation(args); // classname, key
  4230. break;
  4231. case 'menu_selection': case 'clickMenu': // show menu
  4232. e.preventDefault(); e.stopPropagation(); clickMenu();
  4233. break;
  4234. case 'escape':
  4235. if ( $('#top').hasClass('focus_content') && $('#top').hasClass('has_menu') ) { closeMenus(); focusContent(); } else { focusSidebar(); }
  4236. break;
  4237. case 'tab': // escape content_iframe with tab or escape
  4238. focusSidebar();
  4239. break;
  4240. case 'iframe_click': // close menus and fade sidebar
  4241. closeMenus();
  4242. $('body').addClass('focus_content');
  4243. break;
  4244. case 'tab_iframe': // after tabbing into iframe
  4245. if ( $('#text_source:visible').length === 1 ) {
  4246. $('#text_source:visible').focus(); // focus textarea when tabbing into iframe
  4247. let selection = window.getSelection();
  4248. // if data-selection !== 0
  4249. if ( selection.anchorOffset > 0 ) { // restore cursor position or text selection
  4250. // document.getElementById('text_source').setSelectionRange(x,y);
  4251. // document.getElementById('text_source').scrollTop = [position of x];
  4252. return;
  4253. } else {
  4254. document.getElementById('text_source').setSelectionRange(0,0);
  4255. document.getElementById('text_source').scrollTop = 0;
  4256. }
  4257. } else if ( $('#text_preview:visible').length === 1 ) {
  4258. $('#text_preview').attr('tabindex',-1).focus().click(); // else focus preview to allow arrow scrolling....
  4259. }
  4260. if ( $('#tbody').length === 1 ) {
  4261. if ( $('tr.selected, tr.dimmed').length === 0 ) {
  4262. $('#tbody').find('tr:visible a').first().click(); // select first row when tabbing into directory
  4263. } else {
  4264. $('.dimmed').toggleClass('selected dimmed');
  4265. }
  4266. }
  4267. break;
  4268. case 'reload': // escape content_iframe and close content
  4269. $('#reload_btn').click();
  4270. break;
  4271. case 'close': // escape content_iframe and close content
  4272. $('#close_btn').click();
  4273. break;
  4274. case 'iframe_play_pause_media':
  4275. playPauseMedia();
  4276. break;
  4277. case 'mediaSkip': // allow mediaskip from focuses iframe directory
  4278. mediaSkip(undefined,args);
  4279. break;
  4280. case 'play_prev_next_iframe_audio': // play next iframe track
  4281. if ( args === 'ArrowRight' ) {
  4282. if ( $('.audio.playing').nextAll('.audio.selected').length === 1 ) {
  4283. $('.audio.playing').nextAll('.audio.selected').first().find('a').dblclick();
  4284. } else {
  4285. $('.audio.playing').nextAll('.audio').first().find('a').dblclick();
  4286. }
  4287. }
  4288. break;
  4289. case 'close_iframe_audio':
  4290. $('.playing').removeClass('playing');
  4291. break;
  4292. case 'iframe_edited': // let top know iframe text has been edited
  4293. if ( !$('body#top').hasClass('iframe_edited') ) { $('body#top').addClass('iframe_edited'); }
  4294. break;
  4295. case 'clear':
  4296. $('body#top').addClass('iframe_edited');
  4297. break;
  4298. // toggleUIprefs:
  4299. case 'show_numbers': case 'show_invisibles': case 'alternate_background': case 'ignore_ignored_files':
  4300. $('#iframe_body').toggleClass($message);
  4301. break;
  4302. // MESSAGES FROM IFRAME to TOP
  4303. case 'save_text_selection': // from iframe
  4304. $('.text.selected,.code.selected,.markdown.selected').attr('data-selection_start',args[0]).attr('data-selection_end',args[1]);
  4305. break;
  4306. case 'get_text_selection': // from top to iframe
  4307. $('#content_text').attr('data-selection_start',args[0]).attr('data-selection_end',args[1]);
  4308. break;
  4309. // MESSAGES FROM TOP to IFRAME
  4310. case 'theme_light': case 'theme_dark':
  4311. $('#iframe_body').toggleClass('theme_dark theme_light');
  4312. break;
  4313. case 'split_view': case 'toggle_split':
  4314. $('#toggle_split').click();
  4315. break;
  4316. case 'default_text_view':
  4317. $('#iframe_body').toggleClass('preview_text source_text').removeClass('split_view');
  4318. break;
  4319. case 'toggle_text_editor':
  4320. showTextEditor();
  4321. break;
  4322. case 'iframe_focus_text':
  4323. focusText();
  4324. break
  4325. case 'unloading': // warn iframe that user wants to change iframes
  4326. if ( !$('#iframe_body').hasClass('has_warning') ) { $('#iframe_body').addClass('has_warning').find('#warnings_container').removeClass().addClass('unloading').attr('data-function_name',funcName).attr('data-args',args); }
  4327. break;
  4328. case 'ignore':// from iframe "ignore" button
  4329. $('#content_iframe').removeAttr('src').removeClass('has_content'); $('body').removeClass('iframe_edited faded focus_content'); $content_iframe.blur(); // doFunction(funcName,args);
  4330. break;
  4331. case 'top_show_iframe_dir': // focus iframe, add data new directory url, set title and content height
  4332. showIframeDir(args);
  4333. break;
  4334. case 'top_show_iframe_file': // from doubleclicking iframe dir file
  4335. showIframeFile(args); // replace this with default showContent function?
  4336. break;
  4337. case 'top_open_in_sidebar': // from iframe dir header
  4338. openInSidebar(args);
  4339. break;
  4340. }
  4341. }
  4342. }
  4343. window.addEventListener('message',receiveMessage,false);
  4344. // END MESSAGES
  4345.  
  4346. // WARNINGS
  4347. // list of functions to remember while sending messages and then execute after warning button click
  4348. function doFunction(funcName,args) {
  4349. var funcDictionary = { 'arrowNavigation':arrowNavigation, 'clickRow':clickRow, 'doubleClickRow':doubleClickRow, 'null':null, 'clickMenu':clickMenu, 'clickThis':clickThis, 'clearText':clearText, 'closeButton':closeButton, 'closeContent':closeContent, 'closeFontFile':closeFontFile, 'closePlaylist':closePlaylist, 'closeGlyph':closeGlyph, 'openInContentPane':openInContentPane, 'mediaSkip':mediaSkip, 'resetContent':resetContent, 'setLocation':setLocation, 'showDirectorySource':showDirectorySource };
  4350. return funcName === 'null' ? null : funcDictionary[funcName](args);
  4351. }
  4352. // Open and Close Warning alert, focus default button
  4353. function openWarning(id,buttonid) { $('body').addClass('has_warning').find('#warnings_container').removeClass().addClass(id); focusButton(buttonid); }
  4354. function closeWarning() { $('body').removeClass('has_warning').find('#warnings_container, #warning_buttons button').removeClass(); }
  4355. // Show warning after in certain conditions (edited text, open playlist, open font file, etc.; otherwise do the action.
  4356. function showWarning(funcName,args) {
  4357. switch(true) {
  4358. case ( /arrowNavigation|clickRow/.test(funcName) ): // warnings for arrow navigation and row clicks
  4359. switch(true) {
  4360. case $('body').hasClass('iframe_edited'):
  4361. sendMessage('iframe','unloading',funcName,args.key); // upon receipt of message, iframe will show its warning message, based on the funcName
  4362. focusButton('warning_btn_save');
  4363. break;
  4364. case !$('body').hasClass('focus_content') && /has_font_file|has_glyph/.test( $content_pane.attr('data-content') ): // warn with open font file and focused sidebar
  4365. openWarning('warning_close_font','warning_btn_cancel');
  4366. break;
  4367. default:
  4368. doFunction(funcName,args);
  4369. break;
  4370. }
  4371. break;
  4372. case ( !/arrowNavigation|clickRow/.test(funcName) ): // warnings for other functions
  4373. switch(true) {
  4374. case $content_pane.attr('data-content') === 'has_font_file': // warn with open font file and close button.
  4375. openWarning('warning_close_font','warning_btn_cancel');
  4376. break;
  4377. case $('body').hasClass('has_playlist') && $content_pane.attr('data-content') === undefined && !$content_pane.hasClass('has_audio'):
  4378. case $('body').hasClass('has_filelist') && $content_pane.attr('data-content') === undefined && !$content_pane.hasClass('has_audio'):
  4379. openWarning('warning_close_playlist','warning_btn_cancel');
  4380. break;
  4381. case $('body').hasClass('edited'):
  4382. $('#content_pane').removeClass('has_hidden_text_editor').attr('data-content','has_text_editor');
  4383. openWarning('unloading','warning_btn_save');
  4384. break;
  4385. default:
  4386. doFunction(funcName,args);
  4387. break;
  4388. }
  4389. }
  4390. }
  4391. // Warning buttons: what to do when the user clicks a warning button
  4392. function warningButtons(id) {
  4393. let btn = $(document.getElementById(id));
  4394. let container_el = btn.closest('body');
  4395. switch(id) {
  4396. case 'warning_btn_dont_save': // do the user initiated func without saving the edited text
  4397. if ( window.self !== window.top ) {
  4398. if ( $('#warnings_container').hasClass('unloading') ) {
  4399. clearText(container_el);
  4400. sendMessage('top','ignore'); // remove the irame src and body.iframe_edited class
  4401. }
  4402. } else {
  4403. clearText(container_el);
  4404. $content_pane.removeAttr('data-content');
  4405. $('#content_iframe').removeAttr('src').removeClass('has_content');
  4406. $dir_list.find('.dir.selected a').click(); // reload selected sidebar directory
  4407. }
  4408. closeWarning();
  4409. break;
  4410. case 'warning_btn_cancel':
  4411. closeWarning();
  4412. if ( $body.hasClass('focus_content') ) { focusContent(); }
  4413. break;
  4414. case 'warning_btn_clear': // clear text editor
  4415. closeWarning();
  4416. clearText();
  4417. break;
  4418. case 'warning_btn_save': // save text
  4419. if ( window.top !== window.self ) { sendMessage('top','clear'); }
  4420. container_el.removeClass('edited');
  4421. $('#save_text').click();
  4422. closeWarning();
  4423. break;
  4424. case 'warning_btn_ok':
  4425. switch(true) {
  4426. case $('#warnings_container').hasClass('warning_close_font'): closeFontFile(); closeWarning(); break;
  4427. case $('#warnings_container').hasClass('warning_close_playlist'): closePlaylist(); closeWarning(); break;
  4428. case $('#warnings_container').hasClass('warning_make_playlist'): makePlaylist(); break;
  4429. case $('#warnings_container').hasClass('warning_local_playlist'): closeWarning(); break;
  4430. case $('#warnings_container').hasClass('warning_no_playlist'): closeWarning(); break;
  4431. }
  4432. break;
  4433. }
  4434. }
  4435. // Click Edited Warning Buttons
  4436. $('#warnings_container').on('click','button', function(e) { e.preventDefault(); e.stopPropagation(); warningButtons( $(this).attr('id') ); });
  4437. // Edited Warning overlay: prevent user clicks on rest of UI
  4438. $('body.has_overlay, body.has_warning').on('click mousedown mouseup', function(e) { e.preventDefault(); e.stopPropagation(); return; });
  4439. // Tab Warning Buttons (keyboard event)
  4440. function tabWarningButtons(e) {
  4441. switch(true) {
  4442. case e.shiftKey:
  4443. if ( !$('#warning_buttons').find(':focus,.focus').length || !$('#warning_buttons').find(':focus,.focus').prevAll('button:visible').length ) {
  4444. $('#warning_buttons').find('button:visible').removeClass('focus').last().focus().addClass('focus');
  4445. } else {
  4446. $('#warning_buttons').find(':focus,.focus').removeClass('focus').prevAll('button:visible').first().addClass('focus').focus();
  4447. }
  4448. break;
  4449. default:
  4450. if ( !$('#warning_buttons').find(':focus,.focus').length || !$('#warning_buttons').find(':focus,.focus').nextAll('button:visible').length ) {
  4451. $('#warning_buttons').find('button:visible').removeClass('focus').first().focus().addClass('focus');
  4452. } else {
  4453. $('#warning_buttons').find(':focus,.focus').removeClass('focus').nextAll('button:visible').first().addClass('focus').focus();
  4454. }
  4455. }
  4456. }
  4457. // END WARNINGS
  4458.  
  4459. // PLAYLISTS
  4460. // Open playlist
  4461. $('#menu').on('click','#open_playlist_label', function(e) { e.stopPropagation(); });
  4462. $('#menu').on('change','#open_playlist', function(e) { openFile(e,'playlist'); });
  4463. // Create Playlist items
  4464. const newURL = function(link) {
  4465. try { return new URL(link,document.baseURL); }
  4466. catch(error) { return console.log('This link is invalid. Please check the file.'); }
  4467. };
  4468. function convertPlaylist(items) {
  4469. let preppedIndex = ''; let preppedRow = ''; let rows, info, title, type, time = '0', display_time = '—', link, url, kind = '', display_kind, ext;
  4470. items = items.replace(/\s*#EXTM3U.*\s*/g,'').replace(/^\*\n{2,}/gm,'\n'); // remove header comment and multiple returns
  4471. switch(true) { // create rows based on m3u type;
  4472. case ( /#EXTINF:/.test(items) ):
  4473. type = 'extm3u'; rows = items.split('#EXTINF:'); break; // need to add cases for other extm3u directives
  4474. default:
  4475. type = 'm3u'; rows = items.split('\n'); break;
  4476. }
  4477. for ( let i = 0; i < rows.length; i++ ) {
  4478. let row = rows[i], classes = [];
  4479. switch(true) { // get entry information: title, link, etc.
  4480. case row.length === 0:
  4481. break;
  4482. case type === 'extm3u' && row.trim().length > 0: // extm3u file
  4483. row = row.trim().split('\n');
  4484. info = row[0];
  4485. time = info.slice(0,info.indexOf(',')).replace('undefined','');
  4486. display_time = new Date(time * 1000).toISOString().substr(11, 8);
  4487. if ( display_time === '00:00:00' ) { display_time = '—'; }
  4488. title = info.slice(info.indexOf(',') + 1);
  4489. link = row[1];
  4490. break;
  4491. default: // m3u with urls only
  4492. title = decodeURIComponentSafe(row.slice(row.lastIndexOf('/') + 1));
  4493. link = row;
  4494. break;
  4495. }
  4496. if ( link !== undefined ) { url = newURL(link); }
  4497. switch(true) {
  4498. case url === undefined && i !== 0: // add notice for invalid url and row class
  4499. closeMenus();
  4500. classes.push('ignored','invalid','disabled');
  4501. title += ' [invalid url]';
  4502. case url === undefined:
  4503. break;
  4504. case url.search.length: // remove query string from dirs
  4505. link = link.replace(/\/\?.+/,'/');
  4506. case url.pathname.endsWith('/'): // directory
  4507. ext = ' dir'; kind = 'dir'; classes.push('dir');
  4508. break;
  4509. case /file:/.test(link) && !/file:/.test($protocol):
  4510. classes.push('disabled'); // disable local files on non-local pages
  4511. case link.indexOf('pdf#') > -1: // remove pdf parameters
  4512. link = link.replace(/pdf#.+/,'pdf');
  4513. default: // files
  4514. ext = url.pathname.slice(url.pathname.lastIndexOf('.') + 1); kind = getItemKind(ext);
  4515. switch(true) {
  4516. case kind === 'audio':
  4517. $('body').addClass('has_media has_audio');
  4518. classes.push('file','media',kind,ext);
  4519. $body.removeClass('has_filelist').addClass('has_playlist');
  4520. break;
  4521. case kind === 'video':
  4522. $('body').addClass('has_media has_video');
  4523. classes.push('file','media',kind,ext);
  4524. $body.removeClass('has_filelist').addClass('has_playlist');
  4525. break;
  4526. case kind === 'image': $('body').addClass('has_images');
  4527. case kind === 'font': $('body').addClass('has_fonts');
  4528. default: classes.push('file',kind,ext); $body.removeClass('has_playlist').addClass('has_filelist');
  4529. }
  4530. }
  4531. let display_kind = kind.slice(0,1).toUpperCase() + kind.slice(1);
  4532.  
  4533. if ( i !== 0 ) { // don't list first item twice
  4534. preppedRow = '<tr id="rowid-'+ i +'" class="'+ classes.join(' ') +'" data-kind="'+ kind +'" data-ext="'+ ext +'"><td class="tbody_row_cell tbody_row_cell_name name" data-name="'+ title +'"><a class="tbody_row_cell_name_a icon text_color_111" href="'+ link +'"><span class="tbody_row_cell_name_a_span has_icon_before"><input type="checkbox" tabindex="-1" checked="true">'+ title +'</span></a></td><td class="tbody_row_cell_details size details" data-size="'+ time +'">'+ display_time +'</td><td class="tbody_row_cell_details kind details" data-kind="'+ kind +'">'+ display_kind +'</td><td class="tbody_row_cell_details ext details" data-ext="'+ ext +'"></td></tr>';
  4535. preppedIndex += preppedRow;
  4536. }
  4537. }
  4538. return preppedIndex;
  4539. }
  4540. // Open Playlist/Filelist
  4541. function openPlaylist(files,reader) {
  4542. 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
  4543. let bodyClasses = document.getElementById('top').classList, dataClasses = [];
  4544. for ( let bodyClass of bodyClasses.values() ) { if ( bodyClass.startsWith('has') ) { dataClasses.push( bodyClass ); } } // add original body classes to dataclasses
  4545. $('#tbody').data('dir_list',$('#tbody').html() ).data('dataClasses',dataClasses);
  4546. $body.removeClass(dataClasses.join(' '));
  4547. }
  4548. closeContent(); // close all existing content, because it shouldn't be navigable or selectable with playlist loaded
  4549. let preppedIndex = convertPlaylist(reader.result); // get the new index
  4550. $('#tbody').empty().append(preppedIndex); // append the prepared playlist
  4551. $('#top,#content_pane').removeClass('has_menu faded');
  4552. if ( !$('#sort_by_name').hasClass('selected' ) ) { $('#sort_by_name').click(); } // sort by name
  4553. $('#stats_summary_playlist_files').empty().html('<span>Playlist: '+ $('#tbody tr.media').length +' Files</span>'); // update stats
  4554. if ( /file:/.test(preppedIndex) && !/file:/.test($protocol) ) { // show warning about local files on non-local page
  4555. $body.addClass('has_warning');
  4556. $('tbody').addClass('local');
  4557. openWarning('warning_local_playlist','warning_btn_ok');
  4558. }
  4559. if ( $body.hasClass('autoload_media') ) {
  4560. $('#tbody tr.media:not(.disabled)').first().click(); // select first item
  4561. if ( $body.hasClass('has_images') ) { autoLoadCoverArt(); }
  4562. }
  4563. scrollThis('tbody','selected',false);
  4564. document.title = 'Playlist: '+ files.name;
  4565. $('#parents_dir_nav').find('> div').empty().html( files.name );
  4566. $('#open_playlist').val('');
  4567. }
  4568. // Make and save playlist
  4569. function makePlaylistLink(link) {
  4570. link = link.replace(/\/\?.+/,'/').replace(/\.pdf\#.+/,'pdf');
  4571. switch(true) { // we need to handle various cases like this in case the link is being from a loaded playlist which might include links to other pages
  4572. case link.startsWith('http'): // remote files
  4573. case link.startsWith('file'): // local files
  4574. link = link;
  4575. break;
  4576. case link.startsWith('/'):
  4577. link = $protocol +'//' + link;
  4578. break;
  4579. case !link.startsWith('file'):
  4580. link = $protocol +'//'+ window.location.host + window.location.pathname + link;
  4581. break;
  4582. }
  4583. return link;
  4584. }
  4585. function makePlaylist() {
  4586. let items = $('#tbody'), rows, playlist = [], playlistEntry = '';
  4587. let playlist_type = $('#make_playlist_form').find('input:checked').attr('id');
  4588. switch(playlist_type) {
  4589. case 'media_files_only': rows = items.children('tr.media:not(.unchecked)'); break;
  4590. case 'audio_files_only': rows = items.children('tr.audio:not(.unchecked)'); break;
  4591. case 'video_files_only': rows = items.children('tr.video:not(.unchecked)'); break;
  4592. case 'all_non_media_files': rows = items.children('tr:not(.media)'); break;
  4593. case 'all_items': rows = items.children('tr'); break;
  4594. case 'directories_only': rows = items.children('tr.dir'); break;
  4595. case 'files_only': rows = items.children('tr.file'); break;
  4596. }
  4597. switch(true) { // show warning if no qualifying items found or make playlist
  4598. case rows.length === 0:
  4599. openWarning('warning_no_playlist','warning_btn_ok');
  4600. break;
  4601. default:
  4602. for ( let i = 0; i < rows.length; i++ ) {
  4603. let row = $(rows[i]);
  4604. let link = row.find('a').attr('href');
  4605. link = makePlaylistLink(link);
  4606. let timing = '';
  4607. playlistEntry = '#EXTINF:'+ timing +','+ row.find('a').text() +'\n'+ link;
  4608. playlist.push(playlistEntry);
  4609. }
  4610. playlist = '#EXTM3U\n'+ playlist.join('\n'); // add playlist header id
  4611. saveFile(playlist,'audio/mpeg-url','untitled.m3u'); // show save file dialogue
  4612. closeWarning();
  4613. }
  4614. }
  4615. // Select "Make Playlist" menu item: show make playlist warning list
  4616. $('#make_playlist').on('click',function(e) {
  4617. e.preventDefault(); e.stopPropagation(); closeMenus(); openWarning('warning_make_playlist','warning_btn_ok');
  4618. });
  4619. // Get playlist entry
  4620. function makePlaylistEntry(id) {
  4621. let title, link, duration = '';
  4622. switch(true) {
  4623. case id === 'title': // make link for non-audio items
  4624. title = $('#title span').text();
  4625. switch(true) {
  4626. case $('.content.has_content').attr('id') === 'content_image': // link for images
  4627. link = $('.content.has_content').find('img').attr('src');
  4628. break;
  4629. case $('.content.has_content').attr('id') !== 'content_image': // all other content
  4630. link = $('.content.has_content').attr('src');
  4631. if ($('.content.has_content').attr('id') === 'content_video') { duration = Number.parseInt(getElById('video')[0].duration); }
  4632. break;
  4633. }
  4634. link = makePlaylistLink(link);
  4635. $('#content_playlist').find('textarea').val('#EXTINF:'+ duration +','+ title +'\n'+ link +'\n').focus();
  4636. break;
  4637. case id === 'content_audio_title':
  4638. title = $('#content_audio_title span').text();
  4639. link = $('#audio').attr('src');
  4640. link = makePlaylistLink(link);
  4641. duration = Number.parseInt(getElById('audio')[0].duration);
  4642. $('#content_audio_playlist').find('textarea').val('#EXTINF:'+ duration +','+ title +'\n'+ link +'\n').focus();
  4643. break;
  4644. }
  4645. }
  4646. // Show Playlist Entry
  4647. function showPlaylistEntry(id) {
  4648. makePlaylistEntry(id);
  4649. switch(true) {
  4650. case id === 'title':
  4651. document.getElementById('content_playlist').classList.toggle('has_content');
  4652. selectTextareaContent('content_playlist_textarea');
  4653. break;
  4654. case id === 'content_audio_title':
  4655. document.getElementById('content_audio_playlist').classList.toggle('has_content');
  4656. selectTextareaContent('content_audio_playlist_textarea');
  4657. break;
  4658. }
  4659. }
  4660. $('#title, #content_audio_title').on('click',function() {
  4661. if ( $content_pane.attr('data-content') !== 'has_font_file' && $content_pane.attr('data-content') !== 'has_grid' ) {
  4662. showPlaylistEntry($(this).attr('id'));
  4663. }
  4664. });
  4665. // Open File
  4666. function openFile(e,type) { // type: font or playlist.
  4667. if (window.File && window.FileReader && window.FileList && window.Blob) {
  4668. let files = e.target.files[0];
  4669. let reader = new FileReader();
  4670. if ( type === 'font' ) { reader.readAsArrayBuffer(files); }
  4671. if ( type === 'playlist' ) { reader.readAsText(files); }
  4672. reader.onload = function() {
  4673. if ( type === 'font' ) { openFontFile(files,reader); }
  4674. if ( type === 'playlist' ) { openPlaylist(files,reader); }
  4675. return true;
  4676. };
  4677. } else {
  4678. alert('Can\'t open file: file APIs are not fully supported in this browser.');
  4679. }
  4680. }
  4681. // Save File
  4682. function saveFile(content,mimetype,filename) {
  4683. let blob = new Blob([content], {type: mimetype});
  4684. let downloadEl = window.document.createElement('a');
  4685. downloadEl.href = window.URL.createObjectURL(blob);
  4686. downloadEl.download = filename;
  4687. document.body.appendChild(downloadEl);
  4688. downloadEl.click();
  4689. document.body.removeChild(downloadEl);
  4690. URL.revokeObjectURL(blob);
  4691. }
  4692.  
  4693. })();
  4694.  
  4695. // FINIS! + DEO GRATIAS + //