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, audio and video player, markdown/plain text preview and editing, image and font previews and grid views, sorting, user-defined shortcuts, more.

As of 2019-06-29. See the latest version.

  1. // ==UserScript==
  2. // @name Supercharged Local Directory File Browser
  3. // @version 3.2.5
  4. // @description Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds sidebar and preview pane, keyboard navigation, audio and video player, markdown/plain text preview and editing, image and font previews and grid views, sorting, user-defined shortcuts, more.
  5. // @author Gaspar Schott (Michael Schrauzer) mshroud@gmail.com
  6. // @license GPL-3.0-or-later
  7. // @homepageURL https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser
  8. // @contributionURL https://paypal.me/mschrauzer
  9. // @include file://*/
  10. // @include file://*/?*
  11. // @include *!localhost*/
  12.  
  13. // @require https://code.jquery.com/jquery-latest.min.js
  14.  
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/markdown-it/8.4.2/markdown-it.js
  16. // @require https://cdnjs.cloudflare.com/ajax/libs/markdown-it-footnote/3.0.1/markdown-it-footnote.min.js
  17. // @require https://cdn.jsdelivr.net/npm/markdown-it-toc-done-right@2.1.0/dist/markdown-it-toc-made-right.min.js
  18. // @require https://cdn.jsdelivr.net/npm/markdown-it-sub@1.0.0/dist/markdown-it-sub.min.js
  19. // @require https://cdn.jsdelivr.net/npm/markdown-it-sup@1.0.0/dist/markdown-it-sup.min.js
  20. // @require https://cdn.jsdelivr.net/npm/markdown-it-deflist@2.0.3/dist/markdown-it-deflist.min.js
  21. // @require https://cdn.jsdelivr.net/npm/markdown-it-multimd-table@3.1.3/dist/markdown-it-multimd-table.min.js
  22. // @require https://cdn.jsdelivr.net/npm/markdown-it-center-text@1.0.4/dist/markdown-it-center-text.min.js
  23.  
  24.  
  25. // NOTE: By default, userscripts will not run on file:/// urls, so for this script to work, you will have to enable it in your browser extension settings.
  26. // E.G.: For Tampermonkey in Chrome, go to Chrome the extension page, click the details button on the Tampermonkey pane and check 'Allow access to file URLs'.
  27. // NOTE: Safari does not allow local directories to be browsed, so the script will not work on local directories, but it will work on remote directories (or on local directories through a local server).
  28. // NOTE: This script was developed in Vivaldi, running on Mac OS Mojave. It has been tested in several other Chrome and Gecko-based browsers.
  29. // It has been very minimally tested on Windows and not at all on other OSes or browsers. It should work, but please report any issues.
  30.  
  31. // @namespace https://greatest.deepsurf.us/users/16170
  32. // ==/UserScript==
  33.  
  34. (function() {
  35. 'use strict';
  36. var $ = jQuery;
  37.  
  38. // ***** USER SETTINGS ***** //
  39.  
  40. const $settings = {
  41.  
  42. // Paste your exported settings between the two lines below:
  43. //--------------------------------------------------------//
  44.  
  45. shortcuts: // N.B.: Directory links must end with "/", file links must end with another character.
  46. // You may add as many menus and links as you like; just copy the example below and edit as needed.
  47. // Local directory shortcuts must begin with "file:///"; external shortcuts must begin with the correct protocol ("http://" or "ftp://", etc.).
  48. // Hint: you can add query strings to your urls to override user settings on a site by site basis.
  49. // Note that because of same-origin security concerns, the browser will not allow you to navigate from an external webpage to a local directory.
  50. // (But see this page for possible workarounds: https://stackoverflow.com/questions/39007243/cannot-open-local-file-chrome-not-allowed-to-load-local-resource)
  51. [
  52. {
  53. "menu_title":"My Sample Menu",
  54. "links":
  55. [
  56. { "link_name":"My Directory Link 1", "link":"file:///Path/To/My/Directory/" },
  57. { "link_name":"My Directory Link 2", "link":"file:///Path/To/My/Directory_2/" },
  58. { "link_name":"My External Link", "link":"https://www.mywebpage.com/" },
  59. { "link_name":"My File Link", "link":"file:///Path/To/My/File.ext" },
  60. ]
  61. },
  62. {
  63. "menu_title":"My Second Sample Menu",
  64. "links":
  65. [
  66. { "link_name":"My Directory Link 1", "link":"file:///Path/To/My/Directory/" },
  67. { "link_name":"My Directory Link 2", "link":"file:///Path/To/My/Directory_2/" },
  68. { "link_name":"My External Link", "link":"https://www.mywebpage.com/" },
  69. { "link_name":"My File Link", "link":"file:///Path/To/My/File.ext" },
  70. ]
  71. },
  72. ],
  73.  
  74. UI_font: // Choose an installed font for the UI.
  75. 'lucidagrande, sans-serif',
  76. UI_font_size: // Choose a default UI font size; use any standard CSS units.
  77. '13px',
  78. alternate_background: // If true (default true), alternate sidebar row background color.
  79. true,
  80. ignore_files: // If true (default), ignored files (see "$row_settings" below, after Keybindings and Changelog) will be greyed-out (default) in the file list and will not be loaded in the content pane when selected;
  81. // If false, they will be treated as normal files, so if they are selected, the browser will attempt to download any file types it can't handle (which makes keyboard navigation inconvenient).
  82. true,
  83. hide_ignored_files: // If true, ignored files will be completely hidden from the file list;
  84. // If false (default), ignored files will appear greyed-out.
  85. false,
  86. hide_invisibles: // Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
  87. true,
  88. hide_details: // If true (default), hide file and directory details; if false, show them.
  89. true,
  90. default_sort: // Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
  91. // default = Chrome sorting: dirs on top, files alphabetical.
  92. 'default',
  93. dirs_on_top: // If true, directories will always be listed firs except when sorting by "name" (since otherwise sorting by "name" would equal "default").
  94. // 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.)
  95. false,
  96. apps_as_dirs: // Un*x/Mac OS only: if true, treat apps as directories; allows app contents to be browsed. This is the default behavior for Chrome.
  97. // If false (default), treat apps as ignored files.
  98. false,
  99. dark_theme: // If true (default: false), gives the content pane a dark background, and inverts html and text content.
  100. false,
  101. grid_image_size: // Default = 200
  102. 200,
  103. grid_font_size: // Default = 1
  104. 1,
  105. autoload_media: // If true (defautl: 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).
  106. true,
  107. autoload_index_files: // If true (default: false), automatically select first "index.ext" file found in directory.
  108. false,
  109. use_custom_icons: // if true (default), use custom icons for dirs and files
  110. // if false, use browser/server default icons
  111. true,
  112. // TEXT EDITING SETTINGS
  113. preview_text: // If true (default), show rendered plain text and markdown file load;
  114. // if false, show editable source text on file load.
  115. // Note that split_view = true overrides this setting.
  116. true,
  117. split_view: // If true (default: false), show split view on plain text file load
  118. // if false, use default preview_text setting.
  119. false,
  120. sync_scroll: // Sync scrolling in split view.
  121. false
  122. //--------------------------------------------------------//
  123. // Paste your exported settings between the above two lines.
  124. };
  125.  
  126. // $ROW_TYPES:
  127. // DO NOT DELETE ANY EXISTING CATEGORIES!
  128. // Add file extensions for sorting and custom icon display to the existing categories.
  129. // You can also define your own new categories, but do not add an extension to more than one row_type category.
  130. // Do not add leading "." to the extensions.
  131. const $row_types = {
  132. // myCategory: ['ext1','ext2',],
  133. dir: ['/'],
  134. app: ['app/','app','exe','msi'],
  135. archive: ['zip','rar','cbr','7z','tar','gz','dmg','pkg','archive'],
  136. audio: ['mp3','m4a','aac','aif','aiff','ape','flac','ogg','wav','opus'],
  137. font: ['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'],
  138. graphics: ['indd','idml','indt','icml','ai','eps','pages','qxp','qxb','qxd','mif','sla','dtp','pmd','pub','fm','book','inx'],
  139. htm: ['htm','html','xhtm','xhtml'],
  140. image: ['jpg','jpeg','png','apng','gif','bmp','webp','svg','tif','tiff','psd','raw','dng','cr2','nef','arw'],
  141. markdown: ['md','markdown','mdown','mkdn','mkd','mdwn','mdtxt','mdtext'],
  142. office: ['doc','docx','epub','xls','xlsx','xlm','odt','odf','rtf'],
  143. pdf: ['pdf'],
  144. text: ['txt','log','nfo'],
  145. code: ['bak','bash','c','cnf','codes','conf','csh','cshrc','cson','css','default','dist','example','gemspec','h','hd','ini','js','json','jsx','less','list','local','lock','login','logout','lua','old','php','pl','plist','pth','py','rb','rc','rdoc','sass','sh','strings','tcl','vue','xml','yaml','yml'],
  146. video: ['mpeg','mov','m4v','webm','mp4'],
  147. };
  148.  
  149. // $ROW_SETTINGS: Ignore or Exclude files by extension
  150. const $row_settings = {
  151. ignore: // Files with these extensions will not be loaded if selected in the sidebar (prevents the browser from attempting to download the file).
  152. ['exe','doc','docx','rtf','xls','xlsx','odt','odp','csv','msi','dll','indd','idml','pages','numbers','tif','tiff','raw','dng','cr2','nef','arw','eps','psd','ai','afm','pfb','pfm','tfm','zip','pkg','swf','pls','ics','DS_Store','ds_store','ds_store','alias','dmg','gz','qxp','icon.jpg','thumbs.db','ape','srf','epub'],
  153. exclude: // Files with these exensions will not be inverted in dark mode
  154. ['htm','html','xhtm','xhtml'],
  155. };
  156.  
  157. // ***** END USER SETTINGS ***** //
  158.  
  159. // ## FEATURES INCLUDE:
  160. // - Resizable sidebar and directory/file preview pane.
  161. // - Arrow navigation in sidebar:
  162. // - Up and Down Arrows select next/prev item.
  163. // - Left and Right Arrows select next/prev item of same type.
  164. // - Navigate sidebar by typed string.
  165. // - Show/Hide file details (size (if avail), date modified (if avail), kind, extension).
  166. // - Sort sidebar items by name or file details.
  167. // - Default sort = sort by name with folders on top.
  168. // - Preview all file types supported by browser (html, text, images, pdf, audio, video) plus fonts.
  169. // - Preview and edit markdown and plain text files.
  170. // - Markdown rendered with markdownit.js ( https://github.com/markdown-it/markdown-it ).
  171. // - Uses Github Markdown styles for preview ( https://github.com/sindresorhus/github-markdown-css ), with a few customizations.
  172. // - Support for:
  173. // - TOC creation ( `${toc}` ) ( https://github.com/nagaozen/markdown-it-toc-done-right )
  174. // - Multimarkdown table syntax ( https://github.com/RedBug312/markdown-it-multimd-table )
  175. // - Live checkboxes ( `\[ ], [x]` ), allowed in lists and deflists.
  176. // - Superscript ( `^sup^` ) ( https://github.com/markdown-it/markdown-it-sup )
  177. // - Subscript ( `~sub~` ) ( https://github.com/markdown-it/markdown-it-sub )
  178. // - Definition lists ( https://github.com/markdown-it/markdown-it-deflist; for syntax, see http://pandoc.org/MANUAL.html#definition-lists )
  179. // - Centered text ( `->centered<-` ) ( https://github.com/jay-hodgson/markdown-it-center-text )
  180. // - Footnotes ( https://github.com/markdown-it/markdown-it-footnote )
  181. // - View source text, preview, or split pane with proportional sync scroll.
  182. // - Save edited source text or previewed HTML.
  183. // - Audio and video playback, with shuffle, loop, skip audio +/- 10 or 30 sec via keyboard.
  184. // - Preview other files (e.g., lyrics or cover art) in same directory while playing audio.
  185. // - User setting to autoload cover art (if any images in directory, load "cover.ext" or first image found)
  186. // - Grid view for images and fonts.
  187. // - 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):
  188. // - Light or Dark theme.
  189. // - Bookmarks for local or remote directories.
  190. // - Default image grid size.
  191. // - Default UI font size and font-family.
  192. // - Default UI font and font-size.
  193. // - Default file sorting.
  194. // - Sort with directories on top.
  195. // - Treat apps as directories (MacOS and *nix only)
  196. // - Show or hide invisible files.
  197. // - Show or hide ignored files in the ignored files list (see $row_settings in code below $settings).
  198. // - Show or hide file details.
  199. // - Use custom file icons or browser defaults.
  200. // - Autoload index.ext files.
  201. // - Autoload cover art in directories with audio files.
  202. // - Text editing default view: split, source, or preview.
  203. // - Text editing sync scroll: on or off.
  204.  
  205. // ## KEYBINDINGS (These don't work in all browsers):
  206.  
  207. // - <kbd>Arrow Up/Down</kbd>: Select prev/next item.
  208. // - If audio is playing, and prev/next file is also audio, it will be highlighted but not loaded in the audio player; press return to load it.
  209. // - <kbd>Arrow Left/Right</kbd>: Select prev/next row of the same kind as the current selection.
  210. // - If current selection is a media file, select and begin playback of the next media item.
  211. // - <kbd>Opt/Alt + Arrow Left/Right</kbd>: Skip audio ±10s
  212. // - <kbd>Opt/Alt + Shift + Arrow Left/Right</kbd>: Skip audio ±30s
  213. // - <kbd>Cmd/Ctrl + Arrow Up</kbd>: Go to parent directory
  214. // - <kbd>Cmd/Ctrl + Arrow Down</kbd>: Open selected directory
  215. // - <kbd>Return</kbd>: Open selected directory, select file, or pause/play media.
  216. // - <kbd>Space</kbd>: Pause/Play media files
  217. // - <kbd>Cmd/Ctrl + D</kbd>: Toggle file details (size, date modified) in some index page types.
  218. // - <kbd>Cmd/Ctrl + G</kbd>: Show or Reset Grid
  219. // - <kbd>Cmd/Ctrl + I</kbd>: Toggle Invisibles
  220. // - <kbd>Cmd/Ctrl + Shift + O</kbd>: Open selected item in new window/tab
  221. // - <kbd>Cmd/Ctrl + R</kbd>: Reload grids and previewed content, reset scaled images/fonts, reset media files to beginning.
  222. // - <kbd>Cmd/Ctrl + W</kbd>: Close previewed content (doesn't work in all browsers; use close button instead), or close window if no content is being previewed.
  223. // - <kbd>Cmd/Ctrl + Shift + < or ></kbd>: Scale preview items and grids.
  224.  
  225. // CHANGELOG:
  226.  
  227. // **3.2.4** More Markdown goodness
  228. // **ADDED:** Multimarkdown table syntax (https://github.com/RedBug312/markdown-it-multimd-table)
  229. // **ADDED:** Markdown `->`centered text`<-` (https://github.com/jay-hodgson/markdown-it-center-text)
  230. // **ADDED:** Markdown definition lists (https://github.com/markdown-it/markdown-it-deflist; for syntax, see http://pandoc.org/MANUAL.html#definition-lists)
  231. // **ADDED:** Github Markdown styles for preview (https://github.com/sindresorhus/github-markdown-css), with a few customizations.
  232. // **IMPROVED:** Allow checkboxes in definition lists (in both term and details).
  233. // **FIXED:** Various issues with checkboxes and live updating.
  234. // **FIXED:** Allow empty text files to be edited.
  235. // **FIXED:** Markdown preview: Clicking up arrow on hovered header will scroll to TOC inserted with `${toc}` (if present) or to top of previewed text (wasn't working before).
  236. // **FIXED:** Some long-standing bugs with image grid display.
  237. // **FIXED:** A long-standing bug with clicking left and right arrows in preview pane; only images and fonts should be selected.
  238. // **OTHER:** Improved documentation.
  239.  
  240. // **3.2.3** Markdown extensions
  241. // **ADDED:** Markdown TOC (https://github.com/nagaozen/markdown-it-toc-done-right): Insert ${toc} in your text where you want the TOC to appear.
  242. // **ADDED:** Markdown footnotes (https://github.com/markdown-it/markdown-it-footnote).
  243. // **ADDED:** Markdown subscript (~sub~) (https://github.com/markdown-it/markdown-it-sub).
  244. // **ADDED:** Markdown superscript (^sup^) (https://github.com/markdown-it/markdown-it-sup).
  245. // **IMPROVED:** Added basic HTML head and body elements on save.
  246. // **IMPROVED:** Markdown: made sync-scrolling proportional.
  247. // **IMPROVED:** Markdown table styling.
  248. // **OTHER:** Added 'opus' to the default list of supported audio formats.
  249.  
  250. // 3.2.2 More bug fixes.
  251. // FIXED: Markdown source text was being interpreted as HTML.
  252. // FIXED: Markdown checkboxes added to source weren't "live."
  253. // IMPROVED: Markdown live preview update on input.
  254. // IMPROVED: Markdown Nested blockquote styling.
  255.  
  256. // 3.2.1
  257. // FIXED: A bad bug with saving source text.
  258. // ADDED: Made text split-view resizable.
  259. // IMPROVED: Various UI tweaks for text editing.
  260.  
  261. // 3.2.0
  262. // **NEW!:** Preview, edit, and save Markdown and plain-text files. (Now how much would you pay? Or donate?)
  263. // - Basic Markdown syntax is rendered via markdownit.js (https://github.com/markdown-it/markdown-it).
  264. // - View editable source text or rendered HTML preview, or both in split pane.
  265. // - Live preview of source edits in split-pane view.
  266. // - Render Checkboxes/Checklists with [x] and [ ], with live updating of source text.
  267. // - Save edited source text or rendered HTML.
  268. // - More to come.
  269. // - *Caveat:* You must manually save your work if you want to keep it; autosave is not possible.
  270. // - Moreover, because of security concerns with local files, the editing iframe cannot communicate with the parent index page, or vice versa.
  271. // - * It is therefore entirely possible to navigate away from the page and LOSE YOUR WORK. *
  272. // To help prevent loss of work, the script will attempt to warn you when the editing pane loses focus (if you have made any edits),
  273. // e.g., when you click in the script sidebar or in the browser UI, or when the browser tab/window or the browser itself loses focus.
  274. // But note that if the editing pane is already unfocused, the warning will not be issued.
  275. // ADDED: New user settings: set default view for markdown/text files: source or preview, or split pane view. See user settings for details.
  276. // IMPROVED: Plain text and code file type detection.
  277. // CHANGED: Treat files without extensions as text (code) files. (Extension-less binary files will still initiate a download.)
  278. // FIXED: Malformed URI error if file name included "%".
  279. // FIXED: Some issues with image zoom.
  280. // FIXED: An issue with content pane sizing.
  281. // FIXED: An issue with parent directory navigation.
  282.  
  283. // ***** GENERAL SETUP ***** //
  284.  
  285. // ************************************ //
  286. // DON'T EDIT ANYTHING BELOW THIS LINE. //
  287. // ************************************ //
  288.  
  289. const $userAgent = navigator.userAgent;
  290.  
  291. // PATHS
  292. // Fix "%" error in file name; see https://stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser
  293. function decodeURIComponentSafe(uri, mod) {
  294. var out = '', arr, i = 0, l, x;
  295. typeof mod === "undefined" ? mod = 0 : 0;
  296. arr = uri.split(/(%(?:d0|d1)%.{2})/);
  297. for (l = arr.length; i < l; i++) {
  298. try {
  299. x = decodeURIComponent(arr[i]);
  300. } catch (e) {
  301. x = mod ? arr[i].replace(/%(?!\d+)/g, '%25') : arr[i];
  302. }
  303. out += x;
  304. }
  305. return out;
  306. }
  307. var $href = decodeURIComponentSafe(window.location.href); // complete URL including query string: http://www.host.com/path/to/my_dir/?query_string
  308. var $location = decodeURIComponentSafe( [location.protocol, '//', location.host, location.pathname].join('') );
  309. var $location_arr = $location.split('/');
  310. var $current_dir_path = $location.replace(/\//g,'/<wbr>').replace(/_/g,'_<wbr>').replace(/—/g,'—<wbr>').replace(/\\/g,'/'); // URL w/o query string for display
  311. var $current_dir_name = $location.replace(/%20/g,' ').slice(0,-1);
  312. $current_dir_name = $current_dir_name.slice($current_dir_name.lastIndexOf('/') + 1); // Dir name without parents for display: "my_dir"
  313.  
  314. // QUERY PREFS
  315. var getQueryPrefs = function() { return new URLSearchParams( window.location.search ); };
  316. var $query_prefs = getQueryPrefs();
  317. var $UI_pref_width = $query_prefs.get('width') === null ? '25' : (Math.round(100*($query_prefs.get('width'))/window.innerWidth)).toString(); // number string
  318. var $UI_pref_theme = $query_prefs.get('dark_theme') === null ? $settings.dark_theme : JSON.parse( $query_prefs.get('dark_theme') ); // bool
  319. var $UI_pref_background = $query_prefs.get('alternate_background') === null ? $settings.alternate_background : JSON.parse( $query_prefs.get('alternate_background') ); // bool
  320. var $UI_pref_details = $query_prefs.get('hide_details') === null ? $settings.hide_details : JSON.parse( $query_prefs.get('hide_details') );
  321. var $UI_pref_sort = $query_prefs.get('sort') === null ? $settings.default_sort.toLowerCase() : $query_prefs.get('sort');
  322. var $UI_pref_file = $query_prefs.get('file') === null ? '' : $query_prefs.get('file');
  323. var $UI_pref_autoload_media = $query_prefs.get('autoload_media') === null ? $settings.autoload_media : JSON.parse( $query_prefs.get('autoload_media') );
  324. var $UI_pref_split_view = $settings.split_view;
  325. var $UI_pref_preview_text = $settings.preview_text;
  326. // var $UI_pref_invisibles = $query_prefs.get('hide_invisibles') == null ? $settings.hide_invisibles.toLowerCase() : $query_prefs.get('hide_invisibles');
  327. var $UI_pref_selected = $query_prefs.get('selected') === null ? '' : JSON.parse( $query_prefs.get('selected') );
  328. var $UI_pref_history = $query_prefs.get('history') === null ? '' : $query_prefs.get('history');
  329. var $UI_pref_history_arr;
  330. var $grid_image_size = $settings.grid_image_size ? $settings.grid_image_size : 150;
  331. var $grid_font_size = $settings.grid_font_size ? $settings.grid_font_size : 1;
  332. // set query key/value
  333.  
  334. function setQuery(key, value) {
  335. $query_prefs = getQueryPrefs();
  336. $query_prefs.set( key, value );
  337. updateQuery();
  338. }
  339. // toggle query key
  340. function toggleQuery(key) {
  341. var value;
  342. $query_prefs = getQueryPrefs();
  343. value = $query_prefs.has(key) ? JSON.parse( $query_prefs.get(key) ) : $settings[key];
  344. value === true ? $query_prefs.set( key, 'false' ) : $query_prefs.set( key, 'true' );
  345. updateQuery();
  346. }
  347. // update query string
  348. function updateQuery() {
  349. $query_prefs = decodeURIComponent($query_prefs);
  350. window.history.replaceState({}, document.title, window.location.pathname +'?'+ $query_prefs);
  351. }
  352.  
  353. // Styles for iFrame Directory Index pages
  354. const $fileName = window.location.pathname.slice(window.location.pathname.lastIndexOf('/') + 1);
  355. const $textFiles = $row_types.markdown.concat($row_types.text, $row_types.code);
  356.  
  357. if ( ( window.frameElement !== null || window.top !== window.self ) && window.location.href.endsWith('/') ) {
  358.  
  359. const $custom_iframe_styles = document.createElement("style");
  360. const $font_size_units = $settings.UI_font_size.replace(/\d*/,'');
  361.  
  362. var $iframe_styles
  363. = 'a.up { background:transparent; }' // B
  364. + ':root, html, body { background-color:#BBB; }'
  365. + 'body > table > thead, body > table > tbody > tr, body ul li { background:rgba(221,221,221,0.5); }'
  366. + 'body > table > tbody > tr:nth-of-type(odd), body > ul > li:nth-of-type(odd) { background:rgba(221,221,221,1); }'
  367. + 'body > table > tbody > tr:hover, body > ul > li:hover:not(:first-of-type) { background:#ABABAB; }'
  368. + ':root, html, body { border:0 !important; }'
  369. + 'body > table { border-top:solid 1px #888888; }'
  370. + 'body > ul > li:first-of-type { border-bottom:solid 1px #888888; }'
  371. + 'html, body { border-radius:0; }'
  372. + 'body > table { border-collapse:collapse; }'
  373. + 'html, body { box-sizing:border-box; }'
  374. + '#parentDirLinkBox span { color:transparent; }' // C
  375. + 'a:hover { color:#000000; }'
  376. + 'a, #parentDirLinkBox span:before { color:#111111; }'
  377. + '#parentDirLinkBox span:before { content:"Parent Directory"; }'
  378. + '#UI_goUp a.up:before { display:none; }' // D
  379. + 'a, li { display:block; }'
  380. + '#UI_goUp, #UI_showHidden { display:inline-table; }'
  381. + 'body > table table a:before { float:left; }' // F
  382. + ':root, html, body, * { font-family:'+ $settings.UI_font +'; }'
  383. + 'html, body { font-size:'+ parseFloat($settings.UI_font_size) * 0.875 + $font_size_units +'; }'
  384. + 'body > div, body > table, thead, body p { font-size: 1em; }'
  385. + 'body > ul > li:first-of-type, #parentDirLinkBox span:before, #UI_goUp a { font-weight:bold; }'
  386. + 'html { height:100%; }' // H
  387. + 'body { height:auto; }'
  388. + '#UI_goUp, #UI_showHidden, #UI_showHidden label { height:1.5em !important; }'
  389. + 'body > table > tbody > tr, body > ul > li { line-height:1.5; }' // L
  390. + '#UI_goUp, #UI_showHidden { line-height:2; }'
  391. + 'body > ul { list-style:none; }'
  392. + 'html, body, body div, body > ul > li a, #UI_goUp, #UI_showHidden, #parentDirLink, #parentDirLinkBox { margin:0; }' // M
  393. + 'body > div { margin:1em; }'
  394. + 'body p { margin:1rem; }'
  395. + 'body > ul > li { margin-bottom:0; }'
  396. + '.up { margin-inline-start:0; }'
  397. + '.dir::before, .file > img { margin-inline-end: 6px; }'
  398. + 'body table tr, body ul li, body > table > tbody > tr:hover { outline:0; }' // O
  399. + ':root, html, body, body > div, body > ul > li, .up { padding:0; }' // P
  400. + 'body > table > tbody > tr > td, body > table > thead > tr > th, body > ul > li a, #parentDirLinkBox, #UI_goUp, #UI_showHidden { padding-top:3px; }'
  401. + 'body > table > tbody > tr > td:first-of-type, body > table > thead > tr > th:first-of-type, body > ul > li a { padding-right:0; }'
  402. + 'body > table th:last-of-type, body > table > tbody > tr > td:last-of-type, body > ul > li a, #parentDirLinkBox, #UI_showHidden { padding-right:1em; }'
  403. + '#parentDirLinkBox { padding-bottom:0; }'
  404. + 'body > table > tbody > tr > td, body > table > thead > tr > th, body > ul > li a, #parentDirLinkBox, #UI_goUp, #UI_showHidden { padding-bottom:3px; }'
  405. + 'body > ul { padding-left:0; }'
  406. + 'body > table > tbody > tr > td:first-of-type, body > table > thead > tr > th:first-of-type, body > ul > li a, #parentDirLinkBox, #UI_goUp { padding-left:1em; }'
  407. + 'th:not(:first-of-type) { text-align:right; }' // T
  408. + 'th:first-of-type { text-align:left; }'
  409. + 'a, a:hover, td:hover, li:hover { text-decoration:none !important; }'
  410. + 'a.icon { text-indent:2px; }'
  411. + '#UI_goUp, #UI_showHidden { vertical-align:middle; }' // V
  412. + 'html, body, body > table { width:100%; }' // W
  413. + 'thead th:first-of-type { width:50%; }'
  414. + 'html, body { max-width:100%; }'
  415. + 'html, body { min-width:100%; }';
  416.  
  417. $('body').css({'font-family':$settings.UI_font});
  418. $('body > h1, address, body > hr').remove();
  419. $('body > table > tbody').find('hr').closest('tr').remove();
  420. $('body > table th:contains("Name")').css({'text-align':'left'});
  421. $('body > table th:contains("Size")').css({'text-align':'right'});
  422. // pre type
  423. if ( $('body > pre').length ) {
  424. let $list_items = $('body pre').html();
  425. let $parent_dir = $('body pre').find('a:contains("Parent")').first().attr('href');
  426. $list_items = '<table>' + $list_items.replace(/<a.+?>Parent Directory<\/a>/,'').replace(/^\s*<a.+?<\/a><hr>/g,'').replace(/<a /g,'<tr><td><a ').replace(/\r/g,'</td></tr>').replace(/\s{2,}/g,'</td><td>') + '</table>';
  427. $('body').empty().append($list_items).find('tr:first-of-type').remove();
  428. $('body').prepend('<div id="parentDirLinkBox"><a href="'+ $parent_dir +'">Parent Directory</a></div>');
  429.  
  430. }
  431.  
  432. $custom_iframe_styles.appendChild(document.createTextNode(""));
  433. $custom_iframe_styles.append($iframe_styles);
  434. document.head.appendChild($custom_iframe_styles);
  435.  
  436. return;
  437. // MARKDOWN FILES $row_types.markdown
  438. } else if ( ( window.frameElement !== null || window.top !== window.self ) && ( $textFiles.includes( window.location.href.slice( window.location.href.lastIndexOf('.') + 1 ) ) ) ) {
  439.  
  440. // Styles
  441. const $custom_iframe_MDstyles = document.createElement("style");
  442. var $iframe_MDstyles
  443. = 'body { margin:0; padding:32px 0 0; font-size:1rem; position:relative; }'
  444. + 'body #source, body #preview { margin:0; padding:1rem; height:100%; box-sizing:border-box; position:relative; z-index:1; border:0; overflow-y:scroll; }'
  445. + 'body.preview:not(.split) #source, body.source:not(.split) #preview, li#saveBtn div, .comment { display:none; }'
  446. + 'body.split #preview, body.split #source { width:50%; float:left; }'
  447. + 'body.split #preview { border-left:solid 1px #999; }'
  448. + 'body.split #MDhandle { width:8px; position:absolute; top:0; bottom:0; left:calc(50% - 4px); cursor:col-resize; z-index:11; }'
  449. + 'body:not(.split) #MDhandle { z-index:-1; }'
  450. + '#buttons { margin:-32px 0 0; padding:0; display:block; width:100%; position:fixed; z-index:100; background:#EEE; border-bottom: solid 1px #999; font-size:0.875em; -webkit-user-select: none; -moz-user-select: none; user-select:none; }'
  451. + '#buttons li { margin:4px; padding:4px; width:3.5em; height:100%; display:block; opacity:0.5; list-style-type:none; cursor:pointer; }'
  452. + '#buttons li:hover, body.preview:not(.split) #buttons li#showPreview, body.source:not(.split) #buttons li#showSRC, body.split #buttons li#toggleSplit, body.edited #saveBtn { opacity:1; }'
  453. + '#buttons li#toggleSplit { float:left; width:16px; height: 16px; margin: 4px 0 4px 4px; background:url("data:image/svg+xml;utf8,<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=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' enable-background=\'new 0 0 16 16\' xml:space=\'preserve\'><path fill=\'%23444444\' d=\'M0,0v16h16V0H0z M15,15H8.5V1H15V15z M7.5,15H1V1h6.5V15z\'/></svg>") center no-repeat; }'
  454. + '#buttons li#syncScroll { width:8em; float:left; opacity:1; }'
  455. + '#buttons li#syncScroll input { float:left; }'
  456. + '#buttons li#syncScroll label { width:8em; display:block; font-size:0.875em; line-height:1.5; }'
  457. + '#buttons li#showSRC { float:left; width:16px; height: 16px; margin: 4px 0 4px 8px; background:url("data:image/svg+xml;utf8,<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=\'42px\' height=\'16px\' viewBox=\'0 0 42 16\' enable-background=\'new 0 0 42 16\' xml:space=\'preserve\'><g> <path fill=\'%23444444\' d=\'M0.08,7.02L5.94,3.9l0.43,0.78L1.18,7.42l5.19,2.74l-0.43,0.78L0.08,7.82V7.02z\'/> <path fill=\'%23444444\' d=\'M7.84,16.01H6.82L13.78,0h1.02L7.84,16.01z\'/> <path fill=\'%23444444\' d=\'M21.75,7.87l-5.86,3.12l-0.43-0.78l5.19-2.74l-5.19-2.74l0.43-0.78l5.86,3.12V7.87z\'/> <path fill=\'%23444444\' d=\'M30.98,2.65h-3.63V1.58h8.55v1.07h-3.63v9.65h-1.28V2.65z\'/> <path fill=\'%23444444\' d=\'M36.38,4.71h1.3l0.42-2h0.8v2h2.45v0.99h-2.45v4.63c0,0.74,0.27,1.15,0.96,1.15c0.43,0,1.09-0.21,1.39-0.35 l0.18,0.95c-0.42,0.26-1.18,0.45-1.81,0.45c-1.36,0-1.94-0.7-1.94-2.19V5.71h-1.3V4.71z\'/></g></svg>") left 5px no-repeat; }'
  458. + '#buttons li#showPreview { float:left; width:16px; height: 16px; margin: 4px 0 4px 4px; background:url("data:image/svg+xml;utf8,<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=\'42px\' height=\'16px\' viewBox=\'0 0 42 16\' enable-background=\'new 0 0 42 16\' xml:space=\'preserve\'><g> <path fill=\'%23444444\' d=\'M0.08,7.02L5.94,3.9l0.43,0.78L1.18,7.42l5.19,2.74l-0.43,0.78L0.08,7.82V7.02z\'/> <path fill=\'%23444444\' d=\'M7.84,16.01H6.82L13.78,0h1.02L7.84,16.01z\'/> <path fill=\'%23444444\' d=\'M21.75,7.87l-5.86,3.12l-0.43-0.78l5.19-2.74l-5.19-2.74l0.43-0.78l5.86,3.12V7.87z\'/> <path fill=\'%23444444\' d=\'M30.98,2.65h-3.63V1.58h8.55v1.07h-3.63v9.65h-1.28V2.65z\'/> <path fill=\'%23444444\' d=\'M36.38,4.71h1.3l0.42-2h0.8v2h2.45v0.99h-2.45v4.63c0,0.74,0.27,1.15,0.96,1.15c0.43,0,1.09-0.21,1.39-0.35 l0.18,0.95c-0.42,0.26-1.18,0.45-1.81,0.45c-1.36,0-1.94-0.7-1.94-2.19V5.71h-1.3V4.71z\'/></g></svg>") -24px 5px no-repeat; }'
  459. + '#buttons li#saveBtn { float:right; width:20px; height: 16px; background: url("data:image/svg+xml;utf8,<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=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' enable-background=\'new 0 0 16 16\' xml:space=\'preserve\'><g> <path fill=\'%23444444\' d=\'M16,0v10.02h-1.33V1.33H1.33v8.69H0V0H16z\'/> <path fill=\'%23444444\' d=\'M8.47,16h-0.7l-3.08-3.08l0.94-0.94l1.83,1.83V4.28h1.33v9.53l1.81-1.82l0.94,0.93L8.47,16z\'/></g></svg>") 8px 4px no-repeat; position:relative; }'
  460. + 'body.edited #buttons li#saveBtn { background: url("data:image/svg+xml;utf8,<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=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' enable-background=\'new 0 0 16 16\' xml:space=\'preserve\'><g> <path fill=\'%23DD2222\' d=\'M16,0v10.02h-1.33V1.33H1.33v8.69H0V0H16z\'/> <path fill=\'%23DD2222\' d=\'M8.47,16h-0.7l-3.08-3.08l0.94-0.94l1.83,1.83V4.28h1.33v9.53l1.81-1.82l0.94,0.93L8.47,16z\'/></g></svg>") 8px 4px no-repeat; position:relative; }'
  461. + '#buttons li#saveBtn div { position:relative; top:-9px; left:-24px; color:#999; text-align:right; }'
  462. + '#buttons li#saveBtn:hover div { display:block; }'
  463. + '#buttons li#saveBtn span { width:6rem; padding:4px 6px; background: #EEE; float:right; display:block; border:solid 1px #999; }'
  464. + '#buttons li#saveBtn span:first-of-type { border-bottom-width:0px; }'
  465. + '#buttons li#saveBtn span:hover { color:#444; }'
  466. + '#source { width:100%; height:100%; min-height:100%; display:block; line-height:1.2; overflow-y:visible; font-family:monospace; font-size:1rem; position:relative; top:0;bottom:0; background:#EEE; resize:none; }'
  467. + '#source:focus { outline:none; background:#FFF; }'
  468. + '#preview { background:#FFF; font-size:0.875em; }'
  469. + '#preview pre { border:solid 1px #CCC; border-radius:3px; white-space:pre-wrap; word-break:break-word; font-size:1em; }'
  470. + '#preview .no_list { list-style:none; }'
  471. + '#preview > .no_list { padding:0; }'
  472. + '#preview .text-align-center { text-align:center; }'
  473. + '.markdown-body input[type="checkbox"] { margin-top:0.375em; margin-right:6px; float:left; }'
  474. + 'h1 .uplink,h2 .uplink,h3 .uplink,h4 .uplink,h5 .uplink,h6 .uplink { margin:0; padding:0; display:inline-block; font-size:0.875em; cursor:pointer; transition: opacity 0.25s; opacity:0; }'
  475. + 'h1:hover .uplink,h2:hover .uplink,h3:hover .uplink,h4:hover .uplink,h5:hover .uplink,h6:hover .uplink { transition: opacity 0.25s; opacity:0.5; }'
  476. + '#preview table { font-size:inherit; }'
  477. + '#preview table th { background-color:#EEE; }'
  478. ;
  479.  
  480. $custom_iframe_MDstyles.appendChild(document.createTextNode(""));
  481. $custom_iframe_MDstyles.append($iframe_MDstyles);
  482. document.head.appendChild($custom_iframe_MDstyles);
  483.  
  484. // append github styles
  485. $('head').append('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>');
  486. // END styles
  487.  
  488. // UI BUILD
  489. // Get source text first, then delete default pre, append new elements, set textarea value to source text.
  490. let $srctxt = $('body > pre').length ? $('body > pre').text() : '';
  491. $('body').prepend('<textarea id="source"></textarea><div id="preview" class="markdown-body"></div><div id="MDhandle"></div>').find('pre').first().remove();
  492.  
  493. // UI ELEMENTS: Buttons
  494. const toggleSplitBtn = $('<li id="toggleSplit" title="Toggle Split"></li>');
  495. const syncScrollEl = $('<li id="syncScroll"><input name="syncScroll" type="checkbox"><label for="syncScroll">Sync Scroll</label></li>')
  496. const toggleSrcBtn = $('<li id="showSRC" title="Show Source"></li>');
  497. const togglePreviewBtn = $('<li id="showPreview" title="Show Preview"></li>');
  498. const saveBtn = $('<li id="saveBTN" title=""><div><span id="saveText">Save Source</span><span id="saveHTML">Save HTML</span></div></li>');
  499. const buttonsCont = $('<ul id="buttons"></ul>');
  500. buttonsCont.append(toggleSrcBtn, togglePreviewBtn, toggleSplitBtn, syncScrollEl, saveBtn);
  501. $('body').prepend(buttonsCont);
  502.  
  503. // UI INIT
  504. const $buttons = $('#buttons');
  505. const $source = $('#source');
  506. const $preview = $('#preview');
  507. const $MDhandle = $('#MDhandle');
  508.  
  509. $source.val($srctxt);
  510. $settings.split_view === true ? $('body').addClass("split") : null;
  511. $settings.preview_text === true ? $('body').addClass("preview") : $('body').addClass("source");
  512. $settings.sync_scroll === true ? $('#syncScroll input').prop({checked:true}) : null;
  513.  
  514. // UI FUNCTIONS
  515.  
  516. // UI Resize Source/Preview Panes
  517. var resizeSplit = function (f) {
  518. f.stopPropagation();
  519. var $startX = f.pageX;
  520. var $split_width = $source.width();
  521. var $window_width = window.innerWidth;
  522. $(document).on('mousemove',function(e) {
  523. e.stopPropagation();
  524. e.preventDefault();
  525. var $deltaX = e.pageX - $startX;
  526. if ( e.pageX > 200 && e.pageX < $window_width - 200 ) {
  527. $MDhandle.css({'left':$split_width + $deltaX + -4 + 'px'});
  528. $source.css({'width':$split_width + $deltaX + 'px'});
  529. $preview.css({'width':($window_width - $split_width) - $deltaX + 'px'});
  530. }
  531. });
  532. $(document).on('mouseup',function() {
  533. $(document).off('mousemove');
  534. $split_width = $source.width();
  535. });
  536. }
  537. $MDhandle.on('mousedown', resizeSplit );
  538.  
  539. // Click labels to toggle checkboxes
  540. $preview.add($buttons).on('click','label', function(e) {
  541. e.stopPropagation();
  542. $(this).siblings('input').click();
  543. });
  544.  
  545. // UI Buttons functions
  546. $buttons.on('mousedown','li,span',function(e) {
  547. e.stopPropagation();
  548. $source.off('blur', unsavedWarning);
  549. let $name = decodeURI($fileName);
  550. let $thisId = $(this).attr('id');
  551. let $saveHTMLOpen = '<!DOCTYPE html><html><head><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">';
  552. let $saveHTMLClose = '</body></html>';
  553. $thisId === 'toggleSplit' ? $('body').toggleClass('split').find('#source,#preview,#MDhandle').attr('style','') :
  554. $thisId === 'showSRC' ? $('body').removeClass('split preview').addClass('source').find('#source,#preview,#MDhandle').attr('style','') :
  555. $thisId === 'showPreview' ? $('body').removeClass('split source').addClass('preview').find('#source,#preview,#MDhandle').attr('style','') :
  556. $thisId === 'saveText' ? saveMD( $name, $source.val() ) :
  557. $thisId === 'saveHTML' ? saveMD( $name.slice(0,$name.lastIndexOf('.') + 1) + 'html', $saveHTMLOpen + $preview.html() + $saveHTMLClose ) : null;
  558. });
  559. $buttons.on('mouseup','li,#saveText,#saveHTML',function(e) {
  560. e.stopPropagation();
  561. $source.focus();
  562. $source.on('blur', unsavedWarning);
  563. });
  564. $('body').on('mouseover','#preview,#buttons',function(e) {
  565. e.stopPropagation();
  566. $source.off('blur', unsavedWarning);
  567. });
  568. $('body').on('mouseout','#preview,#buttons',function(e) {
  569. e.stopPropagation();
  570. $source.one('blur', unsavedWarning);
  571. });
  572. $(window).on('resize',function() {
  573. $($source,$preview,$MDhandle).attr('style','');
  574. });
  575.  
  576. // UI Sync Scroll
  577. var el2;
  578. var percentage = function(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)) };
  579. var syncScroll = function (el1) {
  580. el1.getAttribute('id') === 'preview' ? el2 = document.getElementById('source') : el2 = document.getElementById('preview');
  581. if ( document.querySelector('input[name="syncScroll"').checked ) {
  582. el2.scrollTo( 0, (percentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
  583. }
  584. }
  585. $source.on('scroll',function() {
  586. syncScroll(this);
  587. });
  588. $preview.on('scroll',function() {
  589. syncScroll(this);
  590. });
  591. // END UI Functions
  592.  
  593. // MARKDOWN PREVIEW
  594. var MDit = window.markdownit({linkify:false,typography:false,html:true})
  595. // .use(window.markdownitSanitizer)
  596. .use(window.markdownitMultimdTable, {enableMultilineRows: true})
  597. .use(window.markdownitSub)
  598. .use(window.markdownitSup)
  599. .use(window.markdownitFootnote)
  600. .use(window.markdownitCentertext)
  601. .use(window.markdownitDeflist)
  602. .use(window.markdownitTocDoneRight);
  603.  
  604. // Custom pre- and post-processing for text.
  605. var addHeaderIDs = function(match, p1, p2, p3, offset, string) { // create header ids for TOC
  606. return '<h'+ p1 +' id="'+ p3.toLowerCase().replace(/ /g,'-') +'" ' + p2 +'>'+ p3;
  607. }
  608. var customPreProcess = function(src) {
  609. return src;
  610. }
  611. var customPostProcess = function(html) {
  612. 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>')
  613. .replace(/<(p|li|dt|dd)>\-*\s*\[\s{1,}\]\s*(.+?)<\/(p|li|dt|dd)>$/gm,'<$1 class="checklist"><input type="checkbox"><label>$2</label></$3>')
  614. .replace(/^<h(\d)([^>]*)>([^<]+)/gm, addHeaderIDs) // add header IDs;
  615. .replace(/<\/h(\d)>/g,'<span class="uplink">\&uarr;</span></h$1>');
  616. return html;
  617. }
  618. // Render markdown from preprocessed source text
  619. var markdown = function(src) {
  620. let MDpreview = MDit.render( customPreProcess( src ) );
  621. $preview.html( customPostProcess( MDpreview ) ); // set previewed html
  622. }
  623. let $md = $source.length === 0 ? '' : $source.val();
  624. markdown( $md );
  625.  
  626. // EDITING
  627.  
  628. // "Live" Checkboxes
  629. $('input[type="checkbox"]').closest('ul').addClass('no_list');
  630.  
  631. var replaceAt = function(str, replacement, position) {
  632. return str = str.substring(0, position) + replacement + str.substring(position + replacement.length);
  633. }
  634. var replaceNthSubStr = function(str,substr,replacement,index) {
  635. let count = 0;
  636. let found;
  637.  
  638. while ( (found = substr.exec(str)) !== null) {
  639. if ( count === index ) {
  640. return replaceAt(str, replacement, found.index );
  641. } else {
  642. count++;
  643. }
  644. }
  645. }
  646. $preview.on('click','.checklist input',function(e) {
  647. e.stopPropagation();
  648. $('.checklist').removeClass('clicked');
  649. $(this).closest('p,dt,dd').addClass('clicked');
  650. let checkboxLength = $preview.find('.checklist').length;
  651. let thisIndex = $preview.find('.checklist').index( $('.clicked') );
  652. let srctext = $source.val();
  653. let substr = new RegExp(/\[\s*.\s*\]/g);
  654. let replacement = $(this).is(':checked') ? '[x]' : '[ ]';
  655. $source.val( replaceNthSubStr(srctext, substr, replacement, thisIndex) );
  656. addEditedClass();
  657. });
  658.  
  659. // click TOC anchors
  660. $preview.on('click','.table-of-contents a',function(e) {
  661. e.preventDefault();
  662. let thisId = $(this).attr('href');
  663. if ( thisId ) {
  664. $preview.scrollTop( $(thisId).offset().top - 48 );
  665. }
  666. });
  667. $preview.on('click','.uplink',function(e) {
  668. e.stopPropagation();
  669. if ( $preview.find('.table-of-contents').length > 0 ) {
  670. let position = $preview.find('.table-of-contents').position();
  671. document.getElementsByClassName('table-of-contents')[0].scrollIntoView(true);
  672. } else {
  673. document.getElementById('preview').scroll(0,0);
  674. }
  675. });
  676.  
  677. // Live preview, add edited warning
  678. $source.on('input', function(e) {
  679. addEditedClass();
  680. markdown( $source.val() );
  681. });
  682. var addEditedClass = function() {
  683. if ( !$source.hasClass('edited') ) {
  684. $('#source, body').addClass('edited');
  685. }
  686. }
  687. var unsavedWarning = function() {
  688. if ( $('#source, body').hasClass('edited') ) {
  689. let r = window.confirm('Warning! You have unsaved changes. Do you want to save the file before closing?');
  690. if ( r == true ) {
  691. $('#saveText').trigger('mousedown');
  692. } else {
  693. //$('#source').one('blur',unsavedWarning );
  694. }
  695. }
  696. }
  697. $source.on('blur', unsavedWarning);
  698.  
  699. // SAVE SOURCE or HTML
  700. var saveMD = function(filename, data) {
  701. var blob = new Blob([data], {type: 'text/plain'});
  702. var temp = window.document.createElement('a');
  703. temp.href = window.URL.createObjectURL(blob);
  704. temp.download = filename;
  705. document.body.appendChild(temp);
  706. $(temp).on('mousedown mouseup',function(e){
  707. e.stopImmediatePropagation();
  708. $source.off('blur', unsavedWarning);
  709. })
  710. temp.click();
  711. document.body.removeChild(temp);
  712. URL.revokeObjectURL(blob);
  713. $('body,#source').removeClass('edited');
  714. }
  715. } // END IFRAME FUNCTIONS
  716.  
  717. // Don't run script in previewed contentEditable text and html files.
  718. if ( window.location.pathname.slice(-1) != '/') {
  719. return;
  720. }
  721.  
  722. // Globals
  723. var e, i, j, n;
  724.  
  725. // ***** SET UP UI ELEMENTS ***** //
  726.  
  727. const $body = $('body');
  728. $body.attr('lang','en').find('> h1:contains("Index of"),> #parentDirLinkBox,> #UI_goUp,#UI_showHidden').remove();
  729. $('head').prepend('<meta charset="utf-8">');
  730.  
  731. // ***** SIDEBAR ELEMENTS ***** //
  732. const $parent_dir_menu = $('<nav id="parent_dir_menu"><a href="">&nbsp;</a></nav>');
  733. const $parents_dir_menu = $('<nav id="parents_dir_menu"><div></div></nav><ul class="menu"></ul>');
  734. const $shortcuts_menu = $('<nav id="shortcuts_menu"><div>&nbsp;</div></nav><ul id="shortcuts" class="menu"></ul>');
  735. const $details_btn = $('<button id="details_btn" tabindex="-1"><span>Show details</span><span>Hide details</span></button>');
  736. const $inv_checkbox = $('<label ><input type="checkbox" id="inv_checkbox" for="inv_checkbox" name="inv_checkbox" tabindex="-1" />Hide Invisibles</label>');
  737. const $grid_btn = $('<div id="grid_btn" tabindex="-1" title="Show Grid"><ul class="menu"><li id="show_image_grid">Show Image Grid</li><li id="show_font_grid">Show Font Grid</li></ul></div>');
  738. const $sidebar_header = $('<table id="sidebar_header"><thead><tr><th colspan="3">INDEX OF</th></tr></thead><tbody><tr><td></td><td></td><td></td></tr><tr><td colspan="3"></td></tr></tbody></table>');
  739. const $dir_list_head = $('<thead id="thead"><tr id="theader" class="header"><th id="name" class="name"><input id="play_toggle" type="checkbox" tabindex="-1" checked="true" />Name</th><th id="size" class="details">Size</th><th id="date" class="details">Date</th><th id="kind" class="details">Kind</th><th id="ext" class="details">Ext</th><th id="default" class="details">Default</th></tr></thead>');
  740. const $dir_list_body = $('<tbody id="tbody"></tbody>');
  741. var $dir_list = $('<table id="dir_list"></table>');
  742. const $dir_list_wrapper = $('<div id="dir_list_wrapper"></div>');
  743. const $sidebar = $('<div id="sidebar"></div>');
  744. const $handle = $('<div id="handle"></div>');
  745. const $sidebar_wrapper = $('<td id="sidebar_wrapper"></td>');
  746. const $toggle_sidebar = $('<div id="toggle_sidebar"></div>');
  747.  
  748. // ***** CONTENT PANE ELEMENTS ***** //
  749. const $content_grid = $('<div id="content_grid" data-grid-scale-factor="0" data-kind="grid"></div>');
  750. const $image_grid_item_el = $('<div class="image_grid_item"><a href=""><img src="/" /></a></div>');
  751. const $font_grid_item_el = $('<div class="font_grid_item"></div>');
  752. const $content_scale = $('<div id="scale"><span id="increase"></span><span id="decrease"></span></div>');
  753. const $sample_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789 [(!@#$%^&*;:)]';
  754. const $lorem_string = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
  755. const $specimen = $('<div class="specimen" style="font-size:3em;">'+ $sample_string +'</div><div class="lorem first" style="font-size:1em;">'+ $lorem_string +'</div><div class="lorem" style="font-size:1em;">'+ $lorem_string +'</div><div class="lorem" style="font-size:1em;">'+ $lorem_string +'</div>');
  756. const $content_font = $('<div id="content_font" spellcheck="false" contenteditable="true" data-kind="font"></div>');
  757. const $image = $('<img class="" data-kind="image" />');
  758. const $content_image = $('<div id="content_image" data-image-scale-factor="1" data-kind="image"></div>');
  759. const $content_pdf = $('<embed id="content_pdf" name="plugin" type="application/pdf" tabindex="0" data-kind="pdf"></embed>');
  760. const $content_iframe = $('<iframe id="content_iframe" sandbox="allow-scripts allow-modals" tabindex="0" data-kind="file"></iframe>');
  761. const $content_video = $('<video id="content_video" class="media" controls data-kind="video">Your browser does not support the video tag.</video>');
  762. const $content_audio_title = $('<tr id="content_audio_title"><td colspan="3"></td></tr>');
  763. const $content_audio = $('<tr id="content_audio"><td colspan="3"></td></tr>');
  764. const $prev_track = $('<div id="prev_track" class="prev_next_track_btn" title="Previous track"></div>');
  765. const $next_track = $('<div id="next_track" class="prev_next_track_btn" title="Next track"></div>');
  766. const $audio = $('<audio id="audio" preload="auto" tabindex="0" controls>Sorry, your browser does not support HTML5 audio.</audio>');
  767. const $loop = $('<label><input type="checkbox" id="loop" for="loop" name="loop" tabindex="0" />Loop</label>');
  768. const $shuffle = $('<label><input type="checkbox" id="shuffle" for="shuffle" name="shuffle" tabindex="0" />Shuffle</label>');
  769. const $close_audio = $('<div id="close_audio" title="Close audio"></div>');
  770. const $checkbox_cont = $('<div id="checkbox_div"></div>');
  771. const $content_reload_btn = $('<td><button id="reload_btn" tabindex="-1">Reload</button></td>');
  772. const $content_stop_btn = $('<td><button id="stop" tabindex="-1">Stop</button></td>');
  773. const $content_title = $('<tr id="content_title"><td id="title"></td></tr>');
  774. const $content_close_btn = $('<td><button id="close_btn" tabindex="-1">Close</button></td>');
  775. const $content_header = $('<header id="content_header"><table><tbody></tbody></table></header>');
  776. const $prev_btn = $('<div class="nav_btn" id="prev_btn"></div>');
  777. const $next_btn = $('<div class="nav_btn" id="next_btn"></div>');
  778. const $content_container = $('<section id="content_container"></section>');
  779. const $content_pane = $('<td id="content_pane"></td>');
  780.  
  781. // SVG UI ICONS
  782. const $svg_prefix = 'url("data:image/svg+xml;utf8,<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\' ';
  783. const $up_arrow = $svg_prefix + 'width=\'12.728px\' height=\'7.779px\' viewBox=\'0 0 12.728 7.779\' enable-background=\'new 0 0 12.728 7.779\' xml:space=\'preserve\'><path fill=\'%23444444\' d=\'M6.364,2.828l4.95,4.949l1.414-1.414L6.364,0l0,0L0,6.363l1.413,1.416L6.364,2.828\'/></svg>")';
  784. const $up_arrow_inv = $svg_prefix + 'width=\'12.728px\' height=\'7.779px\' viewBox=\'0 0 12.728 7.779\' enable-background=\'new 0 0 12.728 7.779\' xml:space=\'preserve\'><path fill=\'%23CCCCCC\' d=\'M6.364,2.828l4.95,4.949l1.414-1.414L6.364,0l0,0L0,6.363l1.413,1.416L6.364,2.828\'/></svg>")';
  785. const $svg_arrow = $svg_prefix + 'width=\'11px\' height=\'16px\' viewBox=\'234.5 248 11 16\' enable-background=\'new 234.5 248 11 16\' xml:space=\'preserve\'><path d=\'M245.5,261l-3,3l-8-8l8-8l3,3l-5,5L245.5,261z\'/></svg>")';
  786. const $toggle = $svg_prefix + 'width=\'13.779px\' height=\'12.729px\' viewBox=\'2.474 -2.475 13.779 12.729\' enable-background=\'new 2.474 -2.475 13.779 12.729\' xml:space=\'preserve\'> <path fill=\'%23444444\' d=\'M5.302,3.889l4.949-4.95L8.838-2.475L2.474,3.889l0,0l6.363,6.364l1.416-1.413L5.302,3.889\'/> <path fill=\'%23444444\' d=\'M11.302,3.889l4.949-4.95l-1.414-1.414L8.474,3.889l0,0l6.363,6.364l1.416-1.413L11.302,3.889\'/> </svg>")';
  787. const $check_mark = $svg_prefix + 'width=\'17px\' height=\'14px\' viewBox=\'250.182 490.01 17 14\' enable-background=\'new 250.182 490.01 16.971 14.143\' xml:space=\'preserve\'><polygon fill=\'%23444444\' points=\'255.839,498.495 253.011,495.667 250.182,498.496 255.839,504.152 267.152,492.838 264.323,490.01 \'/></svg>")';
  788. const $check_mark_inv = $svg_prefix + 'width=\'17px\' height=\'14px\' viewBox=\'250.182 490.01 17 14\' enable-background=\'new 250.182 490.01 16.971 14.143\' xml:space=\'preserve\'><polygon fill=\'%23CCCCCC\' points=\'255.839,498.495 253.011,495.667 250.182,498.496 255.839,504.152 267.152,492.838 264.323,490.01 \'/></svg>")';
  789. const $menu_arrow = $svg_prefix + 'width=\'24px\' height=\'16px\' viewBox=\'0 0 24 16\' enable-background=\'new 0 0 24 16\' xml:space=\'preserve\'> <polygon fill=\'%23444444\' points=\'0,0 13.873,8.008 0.001,16.017 \'/> </svg>")';
  790. const $menu_arrow_inv = $svg_prefix + 'width=\'24px\' height=\'16px\' viewBox=\'0 0 24 16\' enable-background=\'new 0 0 24 16\' xml:space=\'preserve\'> <polygon fill=\'%23CCCCCC\' points=\'0,0 13.873,8.008 0.001,16.017 \'/> </svg>")';
  791. const $menu_icon = $svg_prefix + 'width=\'13px\' height=\'10px\' viewBox=\'0 0 13 10\' enable-background=\'new 0 0 13 10\' xml:space=\'preserve\'><rect fill=\'%23444444\' width=\'13\' height=\'2\'/><rect y=\'4\' fill=\'%23444444\' width=\'13\' height=\'2\'/><rect y=\'8\' fill=\'%23444444\' width=\'13\' height=\'2\'/></svg>")';
  792. const $grid_icon = $svg_prefix + 'width=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' enable-background=\'new 0 0 16 16\' xml:space=\'preserve\'><g><path fill=\'%23666666\' d=\'M5,2v3H2V2H5 M7,0H0v7h7V0L7,0z\'/></g><g><path fill=\'%23666666\' d=\'M14,2v3h-3V2H14 M16,0H9v7h7V0L16,0z\'/></g><g><path fill=\'%23666666\' d=\'M5,11v3H2v-3H5 M7,9H0v7h7V9L7,9z\'/></g><g><path fill=\'%23666666\' d=\'M14,11v3h-3v-3H14 M16,9H9v7h7V9L16,9z\'/></g></svg>")';
  793. const $plus_sign = $svg_prefix + 'width=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' xml:space=\'preserve\'><polygon points=\'16,6.5 9.5,6.5 9.5,0 6.5,0 6.5,6.5 0,6.5 0,9.5 6.5,9.5 6.5,16 9.5,16 9.5,9.5 16,9.5 \'/></svg>")';
  794. const $minus_sign = $svg_prefix + 'width=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' xml:space=\'preserve\'> <rect x=\'1\' y=\'6.499\' width=\'14\' height=\'3.001\'/> </svg>")';
  795. const $next_track_arrow = $svg_prefix + 'width=\'12.8px\' height=\'14px\' viewBox=\'-2 0 12.8 14\' enable-background=\'new -2 0 12.8 14\' xml:space=\'preserve\'><polygon fill=\'%23919191\' points=\'10.873,14.017 0,7.01 10.872,0 \'/><rect x=\'-2\' y=\'0\' fill=\'%23919191\' width=\'2\' height=\'14\'/></svg>")';
  796. const $next_track_arrow_gecko = $svg_prefix + 'width=\'12.8px\' height=\'14px\' viewBox=\'-2 0 12.8 14\' enable-background=\'new -2 0 12.8 14\' xml:space=\'preserve\'><polygon fill=\'white\' points=\'10.873,14.017 0,7.01 10.872,0 \'/><rect x=\'-2\' y=\'0\' fill=\'white\' width=\'2\' height=\'14\'/></svg>")';
  797. const $next_track_arrow_gecko_hover = $svg_prefix + 'width=\'12.8px\' height=\'14px\' viewBox=\'-2 0 12.8 14\' enable-background=\'new -2 0 12.8 14\' xml:space=\'preserve\'><polygon fill=\'%236bb5ff\' points=\'10.873,14.017 0,7.01 10.872,0 \'/><rect x=\'-2\' y=\'0\' fill=\'%236bb5ff\' width=\'2\' height=\'14\'/></svg>")';
  798. const $music = $svg_prefix + 'width=\'143.717px\' height=\'199.404px\' viewBox=\'0 0 143.717 199.404\' enable-background=\'new 0 0 143.717 199.404\' xml:space=\'preserve\'><g opacity=\'0.2\'> <path fill=\'%23757679\' d=\'M143.717,143.82c0,10.033-4.573,18.425-13.717,25.183c-8.394,6.143-17.776,9.211-28.149,9.211 c-6.074,0-11.056-1.432-14.943-4.297c-4.301-3.275-6.45-7.849-6.45-13.719c0-9.279,4.403-17.438,13.204-24.466 c8.326-6.616,17.266-9.93,26.82-9.93c8.052,0,13.922,1.605,17.606,4.812V25.487L63.26,45.654v119.354 c0,10.03-4.573,18.427-13.717,25.181c-8.394,6.142-17.778,9.215-28.148,9.215c-6.077,0-11.055-1.437-14.947-4.302 C2.151,191.827,0,187.253,0,181.386c0-9.282,4.401-17.436,13.206-24.465c8.323-6.615,17.262-9.929,26.817-9.929 c8.051,0,13.921,1.605,17.606,4.812V23.237L143.717,0V143.82z\'/></g></svg>")';
  799. //SVG FILE ICONS
  800. // Chrome default icons
  801. const $file_icon_dir_default = '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= ")';
  802. const $file_icon_file_default = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
  803. // Custom file icons
  804. const $svg_icon_prefix = 'url("data:image/svg+xml;utf8,<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=\'14px\' height=\'14px\' viewBox=\'0 0 14 14\' enable-background=\'new 0 0 14 14\' xml:space=\'preserve\'> ';
  805. const $file_icon_dir = $svg_icon_prefix + '<polygon fill=\'%233399FF\' points=\'6.1,2.7 4.8,1 0,1 0,13 14,13 14,2.7 \'/> <rect x=\'1.5\' y=\'4.2\' fill=\'%2399CCFF\' width=\'11\' height=\'7.3\'/> </svg> ")';
  806. const $file_icon_dir_invisible = $svg_icon_prefix + '<polygon fill=\'%23888888\' points=\'6.1,2.7 4.8,1 0,1 0,13 14,13 14,2.7 \'/> <rect x=\'1.5\' y=\'4.2\' fill=\'%23BBBBBB\' width=\'11\' height=\'7.3\'/> <circle fill=\'%23888888\' cx=\'7\' cy=\'7.9\' r=\'1\'/> </svg> ")';
  807. const $file_icon_app = $svg_icon_prefix + '<g> <polygon style=\'fill:%230066FF;\' points=\'14,0 0,0 0,14 14,14 \'/> </g> <path style=\'fill:%23FFFFFF;\' d=\'M6.466,3.696L5.854,3.421c-0.175-0.078-0.381,0.003-0.455,0.18L5.086,4.348l1.241,0.578l0.315-0.791 C6.71,3.965,6.632,3.771,6.466,3.696z\'/> <polygon style=\'fill:%23FFFFFF;\' points=\'4.955,4.663 2.755,9.922 4.091,10.544 6.201,5.243 \'/> <polygon style=\'fill:%23FFFFFF;\' points=\'2.625,10.237 2.563,12.166 3.955,10.856 \'/> <polygon style=\'fill:%23FFFFFF;\' points=\'9.998,6.663 10.569,8.027 13.143,8.027 13.143,6.663 \'/> <path style=\'fill:%23FFFFFF;\' d=\'M9.838,7.164L7.594,1.797C7.52,1.619,7.314,1.538,7.139,1.616L6.527,1.893 C6.36,1.968,6.282,2.16,6.35,2.329l2.17,5.449L9.838,7.164z\'/> <polygon style=\'fill:%23FFFFFF;\' points=\'9.97,7.479 8.646,8.096 9.021,9.035 10.367,8.43 \'/> <path style=\'fill:%23FFFFFF;\' d=\'M10.479,8.753l-1.3,0.585L9.178,9.339c-0.041,0.311-0.073,0.736,0.073,1.07 c0.35,0.798,1.045,1.264,0.923,1.959c0,0,0.887-1.152,0.989-1.896C11.286,9.579,10.82,9.05,10.479,8.753z\'/> <polygon style=\'fill:%23FFFFFF;\' points=\'5.459,8.027 8.251,8.027 7.708,6.663 6.003,6.663 \'/> <polygon style=\'fill:%23FFFFFF;\' points=\'3.749,6.663 0.857,6.663 0.857,8.027 3.178,8.027 \'/> </svg> ")';
  808. const $file_icon_file = $svg_icon_prefix + '<g> <polygon fill=\'%23888888\' points=\'8.3,0 1.5,0 1.5,14 12.5,14 12.5,4.2 \'/> <polygon fill=\'%23FFFFFF\' points=\'11,12.5 3,12.5 3,1.5 6.8,1.5 6.8,5.7 11,5.7 \'/> <polygon fill=\'%23FFFFFF\' points=\'8.3,4.2 10.2,4.2 8.3,2.2 \'/> </g> </svg> ")';
  809. const $file_icon_text = $svg_icon_prefix + '<g> <polygon style=\'fill:%238888AA;\' points=\'14,0 0,0 0,14 14,14 \'/> </g> <g> <rect x=\'2.155\' y=\'2.187\' style=\'fill:%23FFFFFF;\' width=\'9.69\' height=\'1.14\'/> </g> <g> <rect x=\'2.155\' y=\'5.036\' style=\'fill:%23FFFFFF;\' width=\'9.69\' height=\'1.14\'/> </g> <g> <rect x=\'2.155\' y=\'7.886\' style=\'fill:%23FFFFFF;\' width=\'9.69\' height=\'1.141\'/> </g> <g> <rect x=\'2.155\' y=\'10.736\' style=\'fill:%23FFFFFF;\' width=\'6.555\' height=\'1.14\'/> </g> </svg> ")';
  810. const $file_icon_image = $svg_icon_prefix + '<g id=\'Layer_2\'> </g> <circle fill=\'%23FFEE22\' cx=\'5.5\' cy=\'3.2\' r=\'1.5\'/> <g> <path fill=\'%23FFFFFF\' d=\'M5.6,7.5L3.8,6L0.2,8.7c0.1,0.6,0.6,1.5,0.5,1.3l3-2.4L5.6,9l4.7-4l3.6,3.2l0,0C14,7.8,14,7.4,14,7 c0-0.1,0-0.3,0-0.4l-3.6-3.2L5.6,7.5z\'/> </g> <path fill=\'%2399AADD\' d=\'M3.8,6l1.8,1.5l4.8-4.1L14,6.6C13.8,2.9,10.7,0,7,0C3.1,0,0,3.1,0,7c0,0.6,0.1,1.2,0.2,1.7L3.8,6z\'/> <path fill=\'%233366CC\' d=\'M10.3,5L5.6,9L3.7,7.6l-3,2.4c1.1,2.4,3.5,4,6.3,4c3.4,0,6.3-2.5,6.9-5.8L10.3,5z\'/> <circle fill=\'%23FFE650\' cx=\'5.5\' cy=\'3.2\' r=\'1.5\'/> </svg> ")';
  811. const $file_icon_pdf = $svg_icon_prefix + '<g> <polygon style=\'fill:%23D84444;\' points=\'14,0 0,0 0,14 14,14 14,0 \'/> </g> <path style=\'fill:%23FFFFFF;\' d=\'M12.634,9.094c-0.074,0.047-0.288,0.075-0.423,0.075c-0.439,0-0.981-0.202-1.745-0.529 c0.294-0.022,0.562-0.031,0.803-0.031c0.441,0,0.569,0,1.002,0.108C12.7,8.824,12.705,9.047,12.634,9.094z M4.99,9.162 c0.17-0.3,0.345-0.616,0.521-0.952c0.435-0.822,0.712-1.469,0.914-1.997c0.409,0.742,0.917,1.37,1.51,1.876 C8.011,8.151,8.09,8.212,8.174,8.276C6.962,8.519,5.914,8.809,4.99,9.162z M6.404,1.383c0.241,0,0.38,0.606,0.391,1.179 c0.011,0.568-0.12,0.965-0.287,1.265c-0.14-0.441-0.203-1.129-0.203-1.581C6.305,2.245,6.295,1.383,6.404,1.383z M1.663,12.3 c0.14-0.374,0.68-1.113,1.479-1.771c0.051-0.037,0.175-0.155,0.289-0.263C2.596,11.603,2.033,12.133,1.663,12.3z M12.864,8.31 c-0.24-0.238-0.781-0.363-1.599-0.373c-0.555-0.008-1.218,0.041-1.923,0.138C9.03,7.893,8.707,7.697,8.451,7.459 c-0.683-0.64-1.25-1.524-1.606-2.497c0.021-0.094,0.044-0.171,0.062-0.253c0,0,0.383-2.186,0.28-2.925 c-0.015-0.104-0.021-0.131-0.05-0.21L7.104,1.486c-0.103-0.241-0.31-0.497-0.633-0.483L6.283,0.997H6.28 c-0.358,0-0.654,0.184-0.729,0.456c-0.233,0.864,0.007,2.15,0.444,3.818L5.882,5.544c-0.312,0.76-0.704,1.527-1.048,2.203 L4.787,7.836c-0.362,0.71-0.693,1.315-0.99,1.825l-0.31,0.165c-0.021,0.014-0.551,0.292-0.675,0.367 c-1.053,0.628-1.752,1.343-1.868,1.91c-0.037,0.179-0.009,0.41,0.178,0.52l0.299,0.148c0.129,0.064,0.269,0.096,0.406,0.096 c0.75,0,1.621-0.931,2.817-3.023c1.387-0.452,2.965-0.828,4.347-1.035c1.052,0.595,2.346,1.006,3.163,1.006 c0.146,0,0.271-0.013,0.373-0.042c0.155-0.04,0.288-0.129,0.369-0.254c0.156-0.235,0.191-0.563,0.146-0.901 C13.032,8.519,12.95,8.395,12.864,8.31z\'/> </svg> ")';
  812. const $file_icon_font = $svg_icon_prefix + '<g><polygon style=\'fill:%23770099;\' points=\'14,0 0,0 0,14 14,14 \'/></g><g><path style=\'fill:%23FFFFFF;\' d=\'M4.599,11.321h1.44V2.774H3.334v1.088H1.83V1.222h10.34v2.641h-1.505V2.774H7.977v8.547h1.393v1.457 H4.599V11.321z\'/></g></svg>")';
  813. const $file_icon_code = $svg_icon_prefix + '<g> <polygon style=\'fill:%237722DD;\' points=\'14,0 0,0 0,14 14,14 \'/> </g> <g> <path style=\'fill:%23FFFFFF;\' d=\'M5.892,12.965c-1.049,0-1.784-0.161-2.209-0.48c-0.425-0.317-0.638-0.82-0.638-1.503V8.915 c0-0.446-0.146-0.764-0.438-0.95C2.315,7.777,1.898,7.684,1.351,7.684V6.316c0.547,0,0.967-0.094,1.259-0.28s0.438-0.5,0.438-0.938 V3.006c0-0.675,0.217-1.172,0.65-1.491C4.13,1.195,4.862,1.036,5.893,1.036v1.312c-0.401,0.01-0.718,0.09-0.952,0.24 c-0.233,0.15-0.348,0.426-0.348,0.827V5.4c0,0.876-0.511,1.396-1.532,1.559v0.083c1.021,0.154,1.532,0.67,1.532,1.544v1.997 c0,0.41,0.116,0.688,0.349,0.835c0.233,0.146,0.55,0.223,0.951,0.232L5.892,12.965L5.892,12.965z\'/> <path style=\'fill:%23FFFFFF;\' d=\'M8.045,12.965v-1.313c0.392-0.009,0.706-0.089,0.944-0.239c0.236-0.15,0.355-0.426,0.355-0.829 V8.588c0-0.867,0.511-1.382,1.531-1.545V6.959C9.855,6.795,9.345,6.28,9.345,5.413V3.416c0-0.41-0.116-0.688-0.349-0.834 C8.764,2.436,8.447,2.358,8.045,2.349V1.036c1.049,0,1.785,0.159,2.21,0.479c0.423,0.319,0.637,0.821,0.637,1.505v2.065 c0,0.447,0.146,0.765,0.438,0.951c0.292,0.187,0.711,0.28,1.257,0.28v1.367c-0.546,0.012-0.967,0.107-1.259,0.287 C11.035,8.153,10.89,8.47,10.89,8.915v2.08c0,0.674-0.217,1.172-0.65,1.491C9.808,12.805,9.075,12.965,8.045,12.965z\'/> </g> </svg>")';
  814. const $file_icon_html = $svg_icon_prefix + '<path style=\'fill:%23FFFFFF;\' d=\'M7,14c-3.9,0-7-3.1-7-7C0,3.2,3.1,0,7,0H7C8.9,0,10.6,0.7,12,2C13.3,3.4,14,5.1,14,7 c0,1.9-0.7,3.6-2,5C10.7,13.3,8.9,14,7,14C7,14,7,14,7,14z\'/> <g> <path style=\'fill:%23EE7700;\' d=\'M5.3,1.1C4.7,1.9,4.2,2.6,3.8,3.5C3.4,3.3,3,3.1,2.6,2.8C3.3,2.1,4.2,1.5,5.3,1.1z M2,3.6 c0.5,0.3,1,0.6,1.5,0.8C3.3,5,3.2,5.6,3.1,6.3c0,0.1,0,0.2,0,0.3H0.9C1,5.4,1.4,4.4,2,3.6z M2,10.4c-0.6-0.9-1-1.9-1.1-3h2.1 c0,0.8,0.2,1.5,0.4,2.2C2.9,9.9,2.5,10.1,2,10.4z M2.6,11.2c0.4-0.3,0.8-0.5,1.2-0.6c0.3,0.8,0.8,1.5,1.4,2.2c0,0,0.1,0.1,0.1,0.1 C4.2,12.5,3.3,11.9,2.6,11.2z M6.5,12.9c-0.2-0.2-0.5-0.5-0.7-0.7c-0.5-0.6-0.9-1.3-1.2-1.9C5.3,10,5.9,9.9,6.5,9.9V12.9z M6.5,9 C5.8,9,5,9.1,4.3,9.4C4.1,8.7,4,8.1,4,7.5h2.6V9z M6.5,6.5H4c0-0.1,0-0.1,0-0.2c0.1-0.6,0.2-1.2,0.3-1.7C5.1,4.9,5.8,5,6.5,5V6.5z M6.5,4.1C5.9,4.1,5.3,4,4.7,3.8c0.4-1,1.1-1.9,1.9-2.6V4.1z M12,3.6c0.6,0.9,1,1.9,1.1,3h-2.2c0-0.8-0.2-1.5-0.4-2.2 C11,4.1,11.5,3.9,12,3.6z M11.3,2.7c0,0,0.1,0.1,0.1,0.1c-0.4,0.3-0.8,0.5-1.2,0.7C9.9,2.7,9.4,2,8.8,1.3C8.8,1.3,8.7,1.2,8.7,1.1 C9.7,1.4,10.6,2,11.3,2.7z M7.5,1.2c0.2,0.2,0.5,0.5,0.7,0.7C8.6,2.5,9,3.1,9.3,3.8C8.7,4,8.1,4.1,7.5,4.1V1.2z M7.5,5 c0.7,0,1.5-0.2,2.2-0.4C9.9,5.3,10,5.9,10,6.5H7.5V5z M7.5,7.5H10c0,0.1,0,0.2,0,0.2c0,0.6-0.2,1.1-0.3,1.7C9,9.1,8.2,9,7.5,9V7.5z M7.5,12.9v-3c0.6,0,1.3,0.1,1.9,0.3C8.9,11.2,8.3,12.1,7.5,12.9z M11.3,11.3c-0.7,0.7-1.6,1.3-2.6,1.5c0.6-0.7,1.1-1.5,1.5-2.3 c0.4,0.2,0.8,0.4,1.2,0.6C11.4,11.2,11.4,11.2,11.3,11.3z M10.5,9.7c0.2-0.6,0.3-1.2,0.4-1.9c0-0.1,0-0.2,0-0.3h2.2 c-0.1,1.1-0.4,2.1-1.1,3C11.5,10.1,11,9.9,10.5,9.7z M7,0C3.1,0,0,3.1,0,7s3.1,7,7,7s7-3.1,7-7S10.9,0,7,0z\'/> </g> </svg>")';
  815. const $file_icon_ignored = $svg_icon_prefix + '<path fill=\'%23CCCCCC\' d=\'M7,0C3.1,0,0,3.1,0,7c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7C14,3.1,10.9,0,7,0L7,0z\'/><path fill=\'%23EEEEEE\' d=\'M7,2c2.8,0,5,2.2,5,5s-2.2,5-5,5c-2.8,0-5-2.2-5-5S4.2,2,7,2\'/><rect x=\'0.8\' y=\'5.9\' transform=\'matrix(0.7071 -0.7071 0.7071 0.7071 -2.8818 7.0063)\' fill=\'%23CCCCCC\' width=\'12.5\' height=\'2.3\'/></svg>")';
  816. const $file_icon_ignored_inv = $svg_icon_prefix + '<path fill=\'%23444444\' d=\'M7,0C3.1,0,0,3.1,0,7c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7C14,3.1,10.9,0,7,0L7,0z\'/><path fill=\'%23555555\' d=\'M7,2c2.8,0,5,2.2,5,5s-2.2,5-5,5c-2.8,0-5-2.2-5-5S4.2,2,7,2\'/><rect x=\'0.8\' y=\'5.9\' transform=\'matrix(0.7071 -0.7071 0.7071 0.7071 -2.8818 7.0063)\' fill=\'%23444444\' width=\'12.5\' height=\'2.3\'/></svg>")';
  817. const $file_icon_invisible = $svg_icon_prefix + '<g> <polygon fill=\'%23888888\' points=\'8.3,0 1.5,0 1.5,14 12.5,14 12.5,4.2 \'/> <polygon fill=\'%23BBBBBB\' points=\'11,12.5 3,12.5 3,1.5 6.8,1.5 6.8,5.7 11,5.7 \'/> <polygon fill=\'%23BBBBBB\' points=\'8.3,4.2 10.2,4.2 8.3,2.2 \'/> </g> <circle fill=\'%23777777\' cx=\'7\' cy=\'9\' r=\'1\'/> </svg>")';
  818. // const $file_icon_video = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><style type=\'text/css\'>.st0{fill:%23FFFFFF;}.st1{fill:%23EF6F2E;}</style><rect class=\'st0\' width=\'100\' height=\'100\'/><path class=\'st1\' d=\'M100 100H0V0h100V100zM9.7 90h80.7V10H9.7\'/><path class=\'st1\' d=\'M21 26.4v47.1h58V26.4H21zM31.9 69.9h-7.2v-7.2h7.2V69.9zM31.9 59.1h-7.2v-7.2h7.2V59.1zM31.9 48.2h-7.2v-7.2h7.2V48.2zM31.9 37.3h-7.2v-7.2h7.2V37.3zM42.8 62.7V37.3L60.9 50 42.8 62.7zM75.4 69.9h-7.2v-7.2h7.2V69.9zM75.4 59.1h-7.2v-7.2h7.2V59.1zM75.4 48.2h-7.2v-7.2h7.2V48.2zM75.4 37.3h-7.2v-7.2h7.2V37.3z\'/></svg>")';
  819. // const $file_icon_audio = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><style type=\'text/css\'>.st0{fill:%23FFFFFF;}.st1{fill:%230E3693;}.st2{fill:%23003399;}</style><rect class=\'st0\' width=\'100\' height=\'100\'/><path class=\'st1\' d=\'M100 100H0V0h100V100zM9.7 90h80.7V10H9.7\'/><polyline class=\'st2\' points=\'32.5 37.5 23.5 37.5 23.5 62.5 32.5 62.5 53.6 77 53.6 23 32.5 37.5 \'/><path class=\'st2\' d=\'M71.9 50c0 6.8-3.7 12.7-9.1 15.8l2.8 4.9c7.1-4.1 11.9-11.8 11.9-20.7 0-8.8-4.8-16.6-11.9-20.7l-2.8 4.9C68.2 37.3 71.9 43.2 71.9 50z\'/><path class=\'st2\' d=\'M62.1 50c0 3.2-1.7 5.9-4.3 7.4l2.7 4.7c4.2-2.4 7-6.9 7-12.1 0-5.2-2.8-9.7-7-12.1l-2.7 4.7C60.4 44.1 62.1 46.8 62.1 50z\'/></svg>")';
  820. // const $file_icon_archive = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><style type=\'text/css\'>.st0{fill:%23FFFFFF;}.st1{fill:%23D8A13F;}</style><rect class=\'st0\' width=\'100\' height=\'100\'/><path class=\'st1\' d=\'M100 100H0V0h100V100zM9.7 90h80.7V10H9.7\'/><path class=\'st1\' d=\'M72.4 38.5h-7.9v-7.9L72.4 38.5zM51.1 30.6v28.8h21.4v-19h-9.9v-9.9H51.1zM54.4 23H30.8v5.6h9.3l-5.9 4.5v4.8l8.6-6.6v-2.7h30.1v-2.3L54.4 23zM42.9 35.1l-8.6 6.6v4.8l8.6-6.6V35.1zM34.2 55.1l8.6-6.6v-4.8l-8.6 6.6V55.1zM42.9 57.1v-4.8l-8.6 6.6v2.6h-3.4v5.6h5.3v3.8H33c-0.6-1-1.6-1.6-2.8-1.6 -1.8 0-3.2 1.4-3.2 3.2s1.4 3.2 3.2 3.2c1.2 0 2.2-0.6 2.8-1.6h3.1V77h4.8v-2.9H44c0.6 1 1.6 1.6 2.8 1.6 1.8 0 3.2-1.4 3.2-3.2s-1.4-3.2-3.2-3.2c-1.2 0-2.2 0.6-2.8 1.6h-3.1v-3.8h13.5l18.5-3.3v-2.3H37.1L42.9 57.1z\'/></svg>")';
  821.  
  822.  
  823. // Assemble UI Elements
  824. function assembleUIElements() {
  825. $parents_dir_menu.find('div').append( $current_dir_path );
  826. $sidebar_header.find('thead th').append($toggle_sidebar);
  827. $sidebar_header.find('tbody tr').first().find('td').first().append( $parent_dir_menu ).next().append( $parents_dir_menu ).next().append( $shortcuts_menu );
  828. $sidebar_header.find('tbody tr:last-child td').append( $details_btn, $inv_checkbox, $grid_btn );
  829.  
  830. $dir_list.append($dir_list_head, $dir_list_body);
  831. $sidebar.append($sidebar_header, $dir_list);
  832. $sidebar_wrapper.append( $sidebar, $handle );
  833.  
  834. $content_image.append( $image );
  835. $checkbox_cont.append( $loop, $shuffle );
  836. $content_audio.find('td').append( $prev_track, $next_track, $audio, $close_audio, $checkbox_cont );
  837. // $content_audio_title.find('td').after( $close_audio );
  838. $content_title.prepend($content_reload_btn).append($content_close_btn);
  839. $content_header.find('tbody').append( $content_title, $content_audio_title, $content_audio );
  840. $content_font.append( $specimen );
  841. $content_container.append( $content_header, $content_image, $content_grid, $content_font, $content_pdf, $content_video, $content_iframe, $prev_btn, $next_btn, $content_scale );
  842. $content_pane.append( $content_container );
  843. }
  844. assembleUIElements();
  845.  
  846. //***** STYLES *****//
  847.  
  848. // DEFINE STYLES
  849. var $styles = '';
  850.  
  851. // background-color
  852. $styles += '#sidebar_wrapper, body.light_theme #sidebar, body.light_theme #sidebar ul, body.light_theme #content_header' +
  853. '{ background-color: lightgray }';
  854. $styles += 'body.light_theme #dir_list .selected, body.light_theme #dir_list .playing' + //body.light_theme #dir_list .selected.audio,
  855. '{ background-color: lightsteelblue; }';
  856. $styles += 'body.dark_theme #dir_list .selected' +
  857. '{ background-color: slategray; }';
  858. $styles += 'body.dark_theme #content_grid, #content_image, body.dark_theme #content_pane' +
  859. '{ background-color: #333; }';
  860. $styles += 'body.dark_theme #sidebar ul, body.dark_theme #sidebar_header thead, body.dark_theme #sidebar_header tbody tr:first-of-type, body.dark_theme.alternate_background #dir_list tbody tr:nth-of-type(odd):not(.selected)' +
  861. '{ background-color: #444; }';
  862. $styles += 'body.dark_theme #content_grid:not(.has_grid) .image_grid_item:hover, body.dark_theme #content_grid:not(.has_grid) div.image_grid_item.hovered, body.dark_theme #sidebar, body.dark_theme #content_header, body.dark_theme #dir_list #tbody, body.dark_theme #grid_btn .menu li, body.dark_theme .font_grid_item:hover, body.dark_theme .font_grid_item.hovered, body.dark_theme .image_grid_item:hover, body.dark_theme .image_grid_item.hovered' +
  863. '{ background-color: #555; }';
  864. $styles += 'body.dark_theme #content_grid:not(.has_grid) .image_grid_item.selected, body.dark_theme #sidebar ul li:hover, body.dark_theme #content_grid .font_grid_item.selected, body.dark_theme #content_grid .image_grid_item.selected' +
  865. '{ background-color: #666; }';
  866. $styles += 'body.dark_theme #grid_btn .menu li:hover, body.dark_theme #dir_list tbody tr:hover, body.dark_theme #dir_list tbody li:hover, body.dark_theme.alternate_background #dir_list #tbody tr:hover, body.dark_theme #sidebar .hovered, body.dark_theme #content_iframe[href^="/"], body.dark_theme.alternate_background #dir_list #tbody tr:nth-of-type(odd).hovered' +
  867. '{ background-color: #777; }';
  868. $styles += 'body.light_theme #sidebar_header thead tr, body.light_theme #sidebar_header tbody tr:first-of-type, #parents_dir_menu + ul li:hover, #shortcuts_menu + ul li:not(.has_submenu):hover, #grid_btn.has_images.has_fonts:hover .menu li:hover, body.light_theme #dir_list tbody tr:hover, body.light_theme.alternate_background #dir_list #tbody tr:hover, body.light_theme #dir_list .hovered, body.light_theme.alternate_background #dir_list #tbody tr.hovered' +
  869. '{ background-color: #BBB; }';
  870. $styles += 'body.light_theme #grid_btn .menu li, body.light_theme #content_grid div.selected, body.light_theme.alternate_background #dir_list tbody tr:nth-of-type(odd):not(.selected)' +
  871. '{ background-color: #CCC; }';
  872. $styles += 'body.light_theme #content_grid div:hover, body.light_theme #content_grid div.hovered' +
  873. '{ background-color: #DDD; }';
  874. $styles += 'body.light_theme #dir_list tbody' +
  875. '{ background-color: #DFDFDF; }';
  876. $styles += 'body.light_theme #content_pane, #content_image .img, body.light_theme #content_grid, #content_font, #content_iframe' +
  877. '{ background-color: #FFF; }';
  878. // EXCLUDED: Prevent previewed files with these extensions from being inverted in dark mode
  879. for ( let i = 0, n = $row_settings.exclude.length; i < n; i += 1) {
  880. $styles += 'body.dark_theme #content_iframe[src*="'+ $row_settings.exclude[i] +'" i] { background:#FFF; filter: unset; }';
  881. }
  882. $styles += 'body.dark_theme #content_iframe[src$="/" i]' +
  883. '{ background:#AAA; filter:unset; }';
  884. $styles += '#scale' +
  885. '{ background-color: rgba(128,128,128,0.3); }';
  886. $styles += 'body.is_chrome #audio' +
  887. '{ background-color: rgb(241, 243, 244); }';
  888. $styles += 'body.dark_theme #dir_list .playing, body.dark_theme.alternate_background #dir_list tbody tr.playing:nth-of-type(odd)' + //body.dark_theme #dir_list .selected.audio,
  889. '{ background-color: #4C7E80; }';
  890. $styles += '#dir_list tbody tr, #dir_list .audio a, #dir_list .video a, body.has_audio #content_pane.has_ignored #content_image, body.light_theme #grid_btn .menu, body.dark_theme #grid_btn .menu, body #dir_list tr.file.audio a.icon, body #dir_list tr.file.video a.icon, body.use_custom_icons #dir_list tr.file.ignore a.icon' +
  891. '{ background-color: transparent; }';
  892.  
  893. // BACKGROUND SVG IMAGES & ICONS
  894. $styles += '#parent_dir_menu a { background:' + $up_arrow + 'center no-repeat; }';
  895. $styles += '#shortcuts_menu div { background:'+ $menu_icon + 'center no-repeat; }';
  896. $styles += 'body.light_theme #shortcuts > li.has_submenu { background:'+ $menu_arrow +' right no-repeat; background-size: 12px; }';
  897. $styles += 'body.light_theme #shortcuts > li.has_submenu:hover { background:#BBB '+ $menu_arrow +' right no-repeat; background-size: 12px; }';
  898. $styles += 'body.light_theme #shortcuts li.checked span { background:'+ $check_mark +' 4px center no-repeat; background-size:12px; }';
  899. $styles += '#grid_btn { background:'+ $grid_icon +' center no-repeat; }';
  900. $styles += '#toggle_sidebar { background:'+ $toggle +' center no-repeat; background-size:12px; }';
  901. $styles += '#dir_list thead th.checked { background:'+ $check_mark +' 10px 2px no-repeat; background-size:10px; }';
  902. $styles += 'body.light_theme #dir_list thead th.checked:after { background:'+ $up_arrow +' center no-repeat; background-size:10px; }';
  903. $styles += '#dir_list tbody a { background-size:auto 13px; background-position:6px 4px; }';
  904. $styles += 'body.has_audio #content_pane { background-image: ' + $music +'; background-position: center; background-repeat: no-repeat; background-size:33.33%; }';
  905. $styles += '#prev_track, #next_track { background: rgb(241, 243, 244) '+ $next_track_arrow +' center no-repeat; background-blend-mode:difference; }';
  906. $styles += '#prev_track:hover, #next_track:hover, #close_audio:hover { background-blend-mode:normal; }';
  907. $styles += '#close_audio { background:rgb(241, 243, 244); }';
  908. $styles += '#close_audio:after { background:'+ $plus_sign +' center no-repeat; background-blend-mode:difference; background-size:14px; }';
  909. $styles += '#scale span:first-of-type { background:'+ $plus_sign +' center no-repeat; }';
  910. $styles += '#scale span:last-of-type { background:'+ $minus_sign +' center no-repeat; }';
  911. $styles += '#prev_btn, #next_btn { background: ' + $svg_arrow + ' center no-repeat; }';
  912. $styles += 'body #content_pane.has_ignored { background-image:'+ $file_icon_ignored +'; background-position: center; background-repeat: no-repeat; background-size:50%; }';
  913. // Dark theme
  914. $styles += 'body.dark_theme #content_pane.has_ignored { background-image:'+ $file_icon_ignored_inv +'; background-position: center; background-repeat: no-repeat; background-size:50%; }';
  915. $styles += 'body.dark_theme #shortcuts > li.has_submenu { background:'+ $menu_arrow_inv +' right no-repeat; background-size: 12px; }';
  916. $styles += 'body.dark_theme #shortcuts > li.has_submenu:hover { background:#666 '+ $menu_arrow_inv +' right no-repeat; background-size: 12px; }';
  917. $styles += 'body.dark_theme #shortcuts li.checked span { background:'+ $check_mark_inv +' 4px center no-repeat; background-size:12px; }';
  918. $styles += 'body.dark_theme #dir_list thead th.checked { background:'+ $check_mark_inv +' 10px 2px no-repeat; background-size:10px; }';
  919. $styles += 'body.dark_theme #dir_list thead th.checked:after { background:'+ $up_arrow_inv +' center no-repeat; background-size:10px; }';
  920. // Default File Icons
  921. $styles += 'body.use_default_icons #dir_list .dir a.icon { background:'+ $file_icon_dir_default + ' 6px 4px no-repeat; background-size:auto 13px; }';
  922. $styles += 'body.use_default_icons #dir_list .file a.icon { background:'+ $file_icon_file_default + ' 6px 4px no-repeat; background-size:auto 13px; }';
  923. // Custom File Icons
  924. $styles += 'body.use_custom_icons #dir_list tr.file:not(.dir) a.icon { background: '+ $file_icon_file +' 6px 4px no-repeat; background-size:14px 14px; }';
  925. $styles += 'body.use_custom_icons #dir_list tr.file.audio a.icon, body.use_custom_icons #dir_list tr.file.video a.icon { background: transparent; }';
  926. $styles += 'body.use_custom_icons #dir_list tr.file.font a.icon { background: '+ $file_icon_font +' 6px 4px no-repeat; background-size:14px 14px; }';
  927. $styles += 'body.use_custom_icons #dir_list tr.file.image a.icon { background: '+ $file_icon_image +' 6px 4px no-repeat; background-size:14px 14px; }';
  928. $styles += 'body.use_custom_icons #dir_list tr.file.pdf a.icon { background: '+ $file_icon_pdf +' 6px 4px no-repeat; background-size:14px 14px; }';
  929. $styles += 'body.use_custom_icons #dir_list tr.file[class*="htm"] a.icon { background: '+ $file_icon_html +' 6px 4px no-repeat; background-size:14px 14px; }';
  930. $styles += 'body.use_custom_icons #dir_list tr.file.ignore a.icon { background: '+ $file_icon_ignored +' 6px 4px no-repeat; }';
  931. $styles += 'body.use_custom_icons #dir_list tr.file.invisible a.icon { background: '+ $file_icon_invisible +' 6px 4px no-repeat; background-size:14px 14px; }';
  932. $styles += 'body.use_custom_icons #dir_list tr.dir.invisible a.icon { background: '+ $file_icon_dir_invisible +' 6px 4px no-repeat; background-size:14px 14px; }';
  933. $styles += 'body.use_custom_icons #dir_list tr.dir:not(.app) a.icon { background: '+ $file_icon_dir +' 6px 4px no-repeat; background-size:14px 14px; }';
  934. $styles += 'body.use_custom_icons #dir_list tr.app a { background: '+ $file_icon_app +' 6px 4px no-repeat !important; background-size:14px 14px; }';
  935. $styles += 'body.use_custom_icons #dir_list tr.file.code a.icon { background: '+ $file_icon_code +' 6px 4px no-repeat; background-size:14px 14px }';
  936. $styles += 'body.use_custom_icons #dir_list tr.file.text a.icon,body.use_custom_icons #dir_list tr.file.markdown a.icon { background: '+ $file_icon_text +' 6px 4px no-repeat; background-size:14px 14px }';
  937. // Plain text file icons
  938. for ( let i = 0, n = $row_types.text.length; i < n; i += 1) {
  939. // $styles += 'body.use_custom_icons #dir_list tr.file.'+ $row_types.text[i] +' a.icon { background: '+ $file_icon_text +' 6px 4px no-repeat; background-size:14px 14px }';
  940. }
  941.  
  942. // border
  943. $styles += ':root, html, body, #sidebar_wrapper, #sidebar_header, #dir_list, #main_content, #content_pane, #content_pdf, #content_iframe' +
  944. '{ border: 0 !important; }';
  945. $styles += 'body.dark_theme ul' +
  946. '{ border: solid 1px #222; }';
  947. $styles += '#details_btn' +
  948. '{ border: solid 1px #333; }';
  949. $styles += 'body.light_theme #sidebar ul, body.light_theme .image_grid_item img' +
  950. '{ border: solid 1px gray; }';
  951. // border-top
  952. $styles += 'body.dark_theme #sidebar_header .menu, body.dark_theme #sidebar #grid_btn ul.menu' +
  953. '{ border-top:solid 1px #111; }';
  954. $styles += 'body.dark_theme #dir_list #tbody' +
  955. '{ border-top:solid 1px #222; }';
  956. $styles += 'body.light_theme #dir_list tbody, #grid_btn .menu, .font_grid_item:not(:first-of-type), .image_grid_item + .font_grid_item' +
  957. '{ border-top:solid 1px grey; }';
  958. $styles += '#sort_by' + // , tr.dir ~ tr:not(.dir,.invisible,.ignore)
  959. '{ border-top:solid 1px #999; }';
  960. $styles += 'body.dark_theme.is_dirs_on_top #dir_list tbody tr.dir + tr.file' +
  961. '{ border-top:solid 1px #CCC; }';
  962. // border-right
  963. $styles += '#parents_dir_menu + ul, #shortcuts_menu + ul, body.dark_theme #sidebar #grid_btn ul.menu' +
  964. '{ border-right:0 !important; }';
  965. $styles += 'body.dark_theme #parents_dir_menu, body.dark_theme #sidebar, body.dark_theme #sidebar #grid_btn .menu li' +
  966. '{ border-right:solid 1px #111; }';
  967. $styles += '#sidebar, body.light_theme #parents_dir_menu, #grid_btn .menu li, .image_grid_item' +
  968. '{ border-right:solid 1px grey; }';
  969. // border-bottom
  970. $styles += 'body.dark_theme #sidebar_header thead tr, body.dark_theme #sidebar_header tbody tr:first-of-type' +
  971. '{ border-bottom:solid 1px black; }';
  972. $styles += 'body.dark_theme #sidebar_header .menu, body.dark_theme #content_header, body.dark_theme #sidebar #grid_btn .menu li:first-of-type, body.dark_theme #sidebar #grid_btn ul.menu' +
  973. '{ border-bottom:solid 1px #111; }';
  974. $styles += 'body.dark_theme #content_header tr:first-of-type' +
  975. '{ border-bottom:solid 1px #333; }';
  976. $styles += 'body.light_theme #sidebar_header thead tr, body.light_theme #sidebar_header tbody tr:first-of-type, #grid_btn .menu li:first-of-type, #grid_btn .menu, .specimen, .font_grid_item:last-of-type, .image_grid_item' +
  977. '{ border-bottom:solid 1px grey; }';
  978. $styles += '#content_header tr:first-of-type' +
  979. '{ border-bottom:solid 1px #888; }';
  980. $styles += '#shortcuts_menu + ul > li.has_submenu.ruled, body.is_dirs_on_top #dir_list tbody tr.sorted:not(:last-of-type), .rule' +
  981. '{ border-bottom:solid 1px #999; }';
  982. $styles += 'body.light_theme #content_header' +
  983. '{ border-bottom:solid 1px #AAA; }';
  984. $styles += 'body.dark_theme.is_dirs_on_top #dir_list tbody tr.sorted:not(:last-of-type), body.dark_theme #content_pane.has_font #content_font hr' +
  985. '{ border-bottom:solid 1px #CCC; }';
  986. // border-left
  987. $styles += '#parents_dir_menu + ul, #shortcuts_menu + ul' +
  988. '{ border-left:0; }';
  989. $styles += 'body.dark_theme #parents_dir_menu, body.dark_theme #sidebar #grid_btn ul.menu' +
  990. '{ border-left:solid 1px #111; }';
  991. $styles += 'body.light_theme #parents_dir_menu, #grid_btn .menu' +
  992. '{ border-left:solid 1px grey; }';
  993.  
  994. // border-collapse
  995. $styles += 'table' +
  996. '{ border-collapse: collapse; }';
  997.  
  998. // border-radius
  999. $styles += 'html, body, :root' +
  1000. '{ border-radius: 0; }';
  1001. $styles += '#details_btn' +
  1002. '{ border-radius: 3px; }';
  1003.  
  1004. // bottom
  1005. $styles += '#handle, #parent_dir_menu, #close_audio:after, #content_image, #content_pdf, #content_iframe, #content_grid, #content_font, #prev_btn, #next_btn, #dir_list tbody' +
  1006. '{ bottom: 0; }';
  1007.  
  1008. // box-shadow
  1009. $styles += 'body.light_theme #sidebar ul' +
  1010. '{ box-shadow: 0px 2px 3px -2px #888; }';
  1011. $styles += 'body.dark_theme #sidebar ul' +
  1012. '{ box-shadow:0px 2px 3px -2px #111; }';
  1013. $styles += 'body.dark_theme #sidebar #grid_btn ul.menu' +
  1014. '{ box-shadow:none; }';
  1015.  
  1016. // box-sizing
  1017. $styles += 'html, body, :root, #sidebar, #grid_btn .menu li' +
  1018. '{ box-sizing: border-box; }';
  1019.  
  1020. // clear
  1021. $styles += '#dir_list tbody .name' +
  1022. '{ clear:right; }';
  1023. $styles += '#checkbox_div label, #grid_btn .menu li, #dir_list tbody tr, #content_grid .font_grid_item' +
  1024. '{ clear: both; }';
  1025.  
  1026. // columns
  1027. $styles += '.lorem + .lorem' +
  1028. '{ columns:2; column-gap:2em; }';
  1029. $styles += '.lorem + .lorem + .lorem' +
  1030. '{ columns:3; }';
  1031.  
  1032. // color
  1033. $styles += 'body.dark_theme #shortcuts_menu + ul > li > ul, body.dark_theme #shortcuts_menu + ul > li.has_submenu + li:not(.has_submenu), #content_font' +
  1034. '{ color: #111 }';
  1035. $styles += '#sidebar, #parents_dir_menu + ul li a, body.light_theme #shortcuts_menu + ul li a, body.light_theme #shortcuts_menu + ul li:not(.disabled) > span, body.light_theme #dir_list th:not(.disabled), #dir_list tbody a, #dir_list .selected a, #dir_list .playing a, #content_header, body.dark_theme #details_btn span, .font_grid_item a, .image_grid_item p' +
  1036. '{ color: #111 }';
  1037. $styles += 'body.dark_theme #sidebar_header thead tr, body.dark_theme #sidebar_header tbody tr:first-of-type' +
  1038. '{ color: #444 }';
  1039. $styles += '#scale span:hover' +
  1040. '{ color: #666 }';
  1041. $styles += '#dir_list tr.ignore a, #dir_list tr.ignore.app a, #dir_list tr.ignore td.details' +
  1042. '{ color: #777 }';
  1043. $styles += '#sort_menu .disabled span, #dir_list th.disabled' +
  1044. '{ color: #999 }';
  1045. $styles += 'body.dark_theme #dir_list tr.file.ignore a.icon, body.dark_theme #dir_list tr.ignore td.details' +
  1046. '{ color: #BBB }';
  1047. $styles += 'body.dark_theme .font_grid_item p, body.dark_theme .font_grid_item a, body.dark_theme .image_grid_item p' +
  1048. '{ color: #DDD }';
  1049. $styles += 'body.dark_theme #sidebar th:not(.disabled), body.dark_theme #sidebar td, body.dark_theme #sidebar a, body.dark_theme #content_header tr, body.dark_theme #sidebar tbody tr:first-of-type span' +
  1050. '{ color: #EEE }';
  1051.  
  1052. // content
  1053. $styles += '#dir_list thead th.checked:after, body.has_hidden_sidebar #handle, #dir_list thead th.checked:before, #dir_list thead td.icon, #dir_list tr:empty, #dir_list .audio td.icon, #dir_list .video td.icon, body.hide_invisibles .invisible, body.hide_ignored .ignore, body.use_custom_icons #dir_list tr.file.ignore a.icon::before, #close_audio:after' +
  1054. '{ content:"" }';
  1055. $styles += 'body.dark_theme #dir_list thead th.checked:after' +
  1056. '{ content:" " }';
  1057. $styles += '#content_pane.has_audio #content_audio_title td:before, body #content_pane.has_video #title:before' +
  1058. '{ content:"Playing: " }'; // .audio, .video
  1059. $styles += '#content_pane.has_dir:not(.has_grid) #title:before' +
  1060. '{ content:"Index of: " }'; // .dir
  1061. $styles += '#content_pane.has_grid:not(.has_ignored) #title:before' +
  1062. '{ content:"Fonts and Images from: " }'; // font and image grid
  1063. $styles += 'body:not(.has_images) #content_pane.has_grid:not(.has_ignored) #title:before, #content_pane.has_grid #title.font_grid:before' +
  1064. '{ content:"Fonts from: " }'; // font grid
  1065. $styles += 'body:not(.has_fonts) #content_pane.has_grid:not(.has_ignored) #title:before, #content_pane.has_grid #title.image_grid:before' +
  1066. '{ content:"Images from: " }'; // image grid
  1067. $styles += '#content_pane.has_ignored:not(.has_grid) #title:before' +
  1068. '{ content:"Ignored content: " }'; // .ignored
  1069.  
  1070. // cursor
  1071. $styles += '#sidebar_header thead th, #sort_menu .disabled span, #dir_list .disabled' +
  1072. '{ cursor: default; }';
  1073. $styles += '#sidebar_header tbody tr:first-of-type td:hover, #parents_dir_menu div, #shortcuts_menu div, #grid_btn, #dir_list thead th, #scale span' +
  1074. '{ cursor: pointer; }';
  1075. $styles += '#handle' +
  1076. '{ cursor: col-resize; }';
  1077. $styles += '#content_image img:not(.zoom_img)' +
  1078. '{ cursor: zoom-in; }';
  1079. $styles += '#content_image img.zoom_img' +
  1080. '{ cursor: zoom-out; }';
  1081.  
  1082. // display
  1083. $styles += '#sidebar tbody tr:first-of-type ul, #light_theme, #details_btn span:last-of-type, #grid_btn, #grid_btn:not(:hover) .menu, #dir_list thead .name input, #dir_list .details, body.has_hidden_sidebar #handle, #dir_list thead th.checked:before, #dir_list thead td.icon, #dir_list tr td:not(.name), body.has_hidden_sidebar #dir_list tr td, #dir_list tr:empty, #dir_list tr.audio td.icon, #dir_list tr.video td.icon, body.hide_invisibles #tbody tr.invisible, body.hide_ignored #tbody tr.ignore, #content_title:before, #content_header, #content_audio_title, #content_audio td, #content_pane section header ~ *[id^="content"], .nav_btn, #content_grid, #content_pane.has_hidden_grid #content_grid, #scale, body.light_theme #dark_theme, body.dark_theme #light_theme, body.preview_text #edit_text, body:not(.preview_text) #preview_text, body.use_custom_icons #dir_list img, #dir_list tr.file.audio a img, #dir_list tr.file.video a img' +
  1084. '{ display: none; }';
  1085. $styles += '#sidebar li, #parent_dir_menu, #parent_dir_menu a, #parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu + ul > li:hover > ul, #shortcuts_menu + ul > li > ul:hover, #default, #dir_list tbody a, #content_pane[class^="has_"] #content_header, #content_pane.has_image:hover #scale, #content_pane.has_font:hover #scale, #content_pane.has_font #content_font, #content_pane.has_image #content_image, #content_pane.has_pdf #content_pdf, #content_pane.has_video #content_video, #content_pane.has_iframe #content_iframe, #content_pane.has_dir #content_iframe, #content_pane.has_grid #content_grid.has_grid, #content_pane.has_grid #content_grid.has_font_grid, .font_grid_item, .image_grid_item a, #content_pane.has_grid .nav_btn, #content_pane.has_font .nav_btn, #content_pane.has_image .nav_btn, #scale span, #checkbox_div label, body.dark_theme #dark_theme, body.light_theme #light_theme, body.use_custom_icons #dir_list tr.file.ignore a.icon::before, body:not(.has_hidden_sidebar) #dir_list .name' +
  1086. '{ display: block; }';
  1087. $styles += 'body.has_images.has_fonts #grid_btn:hover ul.menu' +
  1088. '{ display:block !important; }';
  1089. $styles += '#parents_dir_menu div, body.has_fonts #grid_btn, body.has_images #grid_btn, #dir_list thead th.checked:after, #dir_list.show_details th:not(.name), #dir_list.show_details td:not(.name):not(.ext), body.has_audio #dir_list thead input, #content_grid .image_grid_item, #checkbox_div, #prev_track, #next_track, #close_audio, body.dark_theme #dir_list thead th.checked:after, #content_pane[class^="has_"] #content_container' +
  1090. '{ display: inline-block; }';
  1091. $styles += '#content_pane.has_audio #content_audio_title, #content_pane.has_audio #content_audio' +
  1092. '{ display: table-row !important; }';
  1093. $styles += '#shortcuts_menu div, #content_pane[class^="has"] #title, #content_pane[class^="has"] #content_header, #content_pane.has_audio #content_audio td' +
  1094. '{ display: table-cell !important; }';
  1095. $styles += '#content_pane.has_grid #content_grid.has_image_grid' +
  1096. '{ display: grid !important; }';
  1097. $styles += '#content_grid.has_image_grid' +
  1098. '{ grid-gap:0; grid-template-columns:repeat(auto-fill, minmax('+ $grid_image_size +'px, 1fr)); grid-template-rows: repeat(auto-fill, '+ $grid_image_size +'px); }';
  1099. $styles += '#content_pane.has_grid #content_grid.has_image_grid .image_grid_item' +
  1100. '{ display: flex; }';
  1101. $styles += '#thead, #theader, body[class^="is_default"] #tbody tr, body[class^="is_converted"] #tbody tr, body.show_invisibles #tbody tr, body.hide_invisibles #tbody tr:not(.invisible)' +
  1102. '{ display: table; }'; // needed to make rows full width and dir_list scrollable
  1103.  
  1104. // filter
  1105. $styles += '#prev_btn, #next_btn' +
  1106. '{ filter:invert(50%); }';
  1107. $styles += 'body.dark_theme #content_iframe, body.dark_theme #content_font' +
  1108. '{ filter:invert(87.5%); }';
  1109. $styles += '#scale span:hover, body.dark_theme #sidebar #grid_btn, body.dark_theme #grid_btn .menu, body.dark_theme #parent_dir_menu, body.dark_theme #shortcuts_menu, body.dark_theme #toggle_sidebar' +
  1110. '{ filter:invert(100%); }';
  1111.  
  1112. // float
  1113. $styles += '#content_header tr:first-of-type td:first-of-type, #checkbox_div label' +
  1114. '{ float: left; }';
  1115. $styles += '#grid_btn, #content_header tr:first-of-type td:last-of-type' +
  1116. '{ float: right; }';
  1117.  
  1118. // font-family
  1119. $styles += 'html, body, :root' +
  1120. '{ font-family:'+ $settings.UI_font +', arial, "fira sans", helvetica, sans-serif; }';
  1121.  
  1122. // font-size
  1123. $styles += 'html' +
  1124. '{ font-size: 100.001%; }';
  1125. $styles += 'body, :root' +
  1126. '{ font-size:'+ $settings.UI_font_size +'; }';
  1127. $styles += '#sidebar, #sidebar_header, #sidebar_header thead th, #dir_list, #dir_list .details, #content_header, #content_header table' +
  1128. '{ font-size: 0.875rem; }';
  1129. $styles += '#content_grid, .font_grid_item p' +
  1130. '{ font-size: 1rem; }';
  1131. $styles += '#scale span' +
  1132. '{ font-size: 2rem; }';
  1133. $styles += '#content_grid .font_grid_item h2' +
  1134. '{ font-size:'+ $grid_font_size * 4 +'em; }';
  1135. $styles += '#content_font' +
  1136. '{ font-size:'+ $grid_font_size +'em; }';
  1137. $styles += '.lorem.first:first-line' +
  1138. '{ font-size:'+ $grid_font_size*1.33 +'em; }';
  1139.  
  1140. // font-variant
  1141. $styles += '.lorem.first:first-line' +
  1142. '{ font-variant: small-caps }';
  1143.  
  1144. // font-weight
  1145. $styles += '#sidebar_header thead th, h2' +
  1146. '{ font-weight: normal }';
  1147. $styles += '#dir_list .selected a, #dir_list .playing a' +
  1148. '{ font-weight: bold }';
  1149.  
  1150. // height
  1151. $styles += 'html, body, :root, #parent_dir_menu a, #content_container, #main_content, #content_pdf, #content_iframe' +
  1152. '{ height: 100%; }';
  1153. $styles += '#sidebar_header, #parents_dir_menu, #parents_dir_menu div, #content_grid div img, #content_image img:not(.zoom_img)' +
  1154. '{ height: auto; }';
  1155. $styles += '#dir_list thead th.checked:after' +
  1156. '{ height: 8px; }';
  1157. $styles += '#toggle_sidebar' +
  1158. '{ height: 14px; }';
  1159. $styles += '#dir_list td.icon' +
  1160. '{ height: 16px; }';
  1161. $styles += '#grid_btn, body.use_custom_icons #dir_list tr.file.ignore a.icon::before' +
  1162. '{ height: 18px; }';
  1163. $styles += '#audio' +
  1164. '{ height: 32px; }';
  1165. $styles += 'body.dark_theme #dir_list thead th.checked:after' +
  1166. '{ height: 1em; }';
  1167. $styles += '#scale span' +
  1168. '{ height: 2rem; }';
  1169. $styles += '.image_grid_item a' +
  1170. '{ align-self: center; justify-self: center; }';
  1171. $styles += '#content_grid.has_grid .image_grid_item' +
  1172. '{ height: '+ $grid_image_size +'px; }';
  1173. $styles += '#sidebar' +
  1174. '{ height:'+ window.innerHeight +'px; }';
  1175.  
  1176. // max-height
  1177. $styles += '#content_image img.zoom_img' +
  1178. '{ max-height: none; }';
  1179. $styles += '#content_grid div img' +
  1180. '{ max-height:'+ ($grid_image_size - $grid_image_size/8) +'px; }';
  1181. // min-height
  1182. $styles += '#sidebar' +
  1183. '{ min-height: 100%; }';
  1184.  
  1185. // hyphens
  1186. $styles += '#parents_dir_menu div, #content_header td button, #content_font' +
  1187. '{ hyphens: none; }';
  1188. $styles += 'html, body, :root, .lorem' +
  1189. '{ hyphens: auto; }';
  1190.  
  1191. // justify-seld
  1192. $styles += '.image_grid_item' +
  1193. '{ justify-self: stretch; }';
  1194.  
  1195. // left
  1196. $styles += '#sidebar ul, #parent_dir_menu, #dir_list thead, #dir_list tbody, #close_audio:after, #content_header, #content_image, #content_pdf, #content_iframe, #content_grid, #content_font, body.use_custom_icons #dir_list tr.file.ignore a.icon::before, body.has_hidden_sidebar #toggle_sidebar' +
  1197. '{ left: 0; }';
  1198. $styles += '#sidebar #grid_btn ul.menu' +
  1199. '{ left: unset; }';
  1200. $styles += 'body.has_hidden_sidebar #sidebar_wrapper' +
  1201. '{ left: 3px; }';
  1202. $styles += '#shortcuts_menu + ul > li > ul' +
  1203. '{ left: 100%; }';
  1204.  
  1205. // letter-spacing
  1206. $styles += '.lorem.first:first-line, .font_grid_item p' +
  1207. '{ letter-spacing: 0.1em }';
  1208. $styles += '#sidebar_header thead th' +
  1209. '{ letter-spacing: 0.5em }';
  1210.  
  1211. // line-height
  1212. $styles += '.font_grid_item p' +
  1213. '{ line-height: 1 }';
  1214. $styles += '#dir_list tr a, .lorem' +
  1215. '{ line-height: 1.4 }';
  1216.  
  1217. // list-style
  1218. $styles += '#sidebar ul, #grid_btn .menu li' +
  1219. '{ list-style-type: none; }';
  1220.  
  1221. // margin
  1222. $styles += 'html, body, :root, #sidebar ul, #parent_dir_menu, #parent_dir_menu, #parents_dir_menu, #parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu, #grid_btn, #dir_list tbody a, #content_grid p, #content_grid h2' +
  1223. '{ margin: 0; }';
  1224. // margin-top
  1225. $styles += '.image_grid_item + .font_grid_item' +
  1226. '{ margin-top:-1px; }';
  1227. $styles += '#checkbox_div label input, #details_btn' +
  1228. '{ margin-top: 0; }';
  1229. $styles += 'body.has_audio #dir_list input' +
  1230. '{ margin-top: 1px; }';
  1231. // margin-right
  1232. $styles += '#content_video, .image_grid_item a' +
  1233. '{ margin-right: auto; }';
  1234. $styles += '#dir_list thead th.checked' +
  1235. '{ margin-right: 0; }';
  1236. $styles += 'body.has_audio #dir_list input, #reload_btn' +
  1237. '{ margin-right: 8px; }';
  1238. $styles += '#details_btn' +
  1239. '{ margin-right: 0.5em; }';
  1240. $styles += '#dir_list thead th:last-of-type' +
  1241. '{ margin-right: 1em; }';
  1242. $styles += '#checkbox_div' +
  1243. '{ margin-right: calc(-6em - 4px); }';
  1244. // margin-bottom
  1245. $styles += '#details_btn' +
  1246. '{ margin-bottom: 0; }';
  1247. $styles += 'body.has_audio #dir_list input' +
  1248. '{ margin-bottom: 1px; }';
  1249. // margin-left
  1250. $styles += '#content_video, .image_grid_item a' +
  1251. '{ margin-left: auto; }';
  1252. $styles += '#dir_list thead th.checked' +
  1253. '{ margin-left: 0; }';
  1254. $styles += 'body.has_hidden_sidebar #reload_btn' +
  1255. '{ margin-left: 16pt; }';
  1256. $styles += '#details_btn' +
  1257. '{ margin-left: 0.5em; }';
  1258. $styles += '#dir_list tbody tr' +
  1259. '{ margin-inline-start:0; }';
  1260. // -webkit-margin
  1261. $styles += '#sidebar ul' +
  1262. '{ -webkit-margin-before:0em !important; -webkit-margin-after:0em !important; -webkit-padding-start:0em; }';
  1263.  
  1264. // opacity
  1265. $styles += '#content_pane #scale' +
  1266. '{ opacity:0; }';
  1267. $styles += '#scale span:first-of-type, #scale span:last-of-type, #content_pane.has_ignored:before, #close_audio::after' +
  1268. '{ opacity:0.3; }';
  1269. $styles += '#scale span:hover' +
  1270. '{ opacity:0.5; }';
  1271. $styles += '#prev_btn, #next_btn, #close_audio:hover::after' +
  1272. '{ opacity:0.6; }';
  1273. $styles += '#sidebar_header tbody tr:first-of-type td:last-of-type:hover > div, #shortcuts_menu div, #shortcuts_menu div, #parent_dir_menu, #prev_btn, #next_btn, #toggle_sidebar, body.use_custom_icons #dir_list tr.file.ignore a.icon::before' +
  1274. '{ opacity: 0.7; }';
  1275. $styles += '#content_grid div img' +
  1276. '{ opacity:0.8; }';
  1277. $styles += '#grid_btn:hover, #parent_dir_menu:hover, #prev_btn:hover, #next_btn:hover, #toggle_sidebar:hover, #content_pane.has_grid:hover #scale, #content_pane.has_font:hover #content_font ~ #scale, #content_pane.has_image:hover #content_image ~ #scale' +
  1278. '{ opacity:1; }';
  1279.  
  1280. // outline
  1281. $styles += '#grid_btn, #dir_list tbody, #dir_list tbody a, #content_grid .font_grid_item, #content_font' +
  1282. '{ outline: none; }';
  1283.  
  1284. // overflow
  1285. $styles += '#content_container' +
  1286. '{ overflow: visible; }';
  1287. $styles += 'html, body, :root, #parents_dir_menu div, #shortcuts_menu, #dir_list, #dir_list tbody a, #main_content, #close_audio' +
  1288. '{ overflow: hidden; }';
  1289. $styles += '#content_font, #content_grid, #content_image' +
  1290. '{ overflow: auto; }';
  1291. $styles += '#dir_list tbody' +
  1292. '{ overflow-y: auto; }';
  1293. $styles += '#sidebar, #content_font' +
  1294. '{ overflow-wrap: break-word; }';
  1295. $styles += '.lorem' +
  1296. '{ overflow-wrap: normal; }';
  1297.  
  1298. // padding
  1299. $styles += 'html, body, :root, #sidebar_wrapper, #sidebar ul, #parent_dir_menu, #sidebar_header tbody tr td, #parent_dir_menu, #parent_dir_menu a, #parents_dir_menu, #shortcuts_menu, #dir_list .details a, #content_pane, #content_pdf, #content_iframe, audio::-webkit-media-controls-panel' +
  1300. '{ padding: 0; }';
  1301. $styles += '#sidebar_header thead th' +
  1302. '{ padding: 4px; }';
  1303. $styles += '.image_grid_item' +
  1304. '{ padding:6px; }';
  1305. // padding-top
  1306. $styles += '#dir_list thead th, #dir_list .details, #prev_track, #next_track, #close_audio' +
  1307. '{ padding-top: 0; }';
  1308. $styles += '#details_btn, #prev_btn, #next_btn' +
  1309. '{ padding-top: 2px; }';
  1310. $styles += '#parents_dir_menu div, #grid_btn .menu li, #dir_list tbody a, #content_header tr:first-of-type td:first-of-type, #content_header tr:first-of-type td:last-of-type, #title, body #content_audio_title td, #content_audio td' +
  1311. '{ padding-top: 4px; }';
  1312. $styles += '#sidebar_header tbody tr:last-of-type td, #parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu + ul li div span' +
  1313. '{ padding-top: 6px; }';
  1314. $styles += '#content_header table' +
  1315. '{ padding-top: 7px; }';
  1316. $styles += '#content_grid .font_grid_item, .font_grid_item p' +
  1317. '{ padding-top: 0.5rem; }';
  1318. $styles += '#content_image, #content_font div' +
  1319. '{ padding-top: 2rem; }';
  1320. // padding-right
  1321. $styles += '#sidebar_header tbody tr:last-of-type td, #dir_list .details, #prev_track, #next_track, #close_audio' +
  1322. '{ padding-right: 0; }';
  1323. $styles += '#parents_dir_menu div, #details_btn, #grid_btn .menu li, #dir_list tbody a, #content_header tr:first-of-type td:first-of-type, #content_header tr:first-of-type td:last-of-type, #content_audio td, #close_audio' +
  1324. '{ padding-right: 6px; }';
  1325. $styles += '#parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu + ul li div span' +
  1326. '{ padding-right: 8px; }';
  1327. $styles += '#dir_list tbody tr, #content_header table, #title, #prev_btn, #next_btn' +
  1328. '{ padding-right: 12px; }';
  1329. $styles += '#grid_btn .menu, #dir_list thead th, #content_audio_title td' +
  1330. '{ padding-right: 29px; }';
  1331. $styles += '#content_grid .font_grid_item, #content_image, #content_font div' +
  1332. '{ padding-right: 2.5rem; }';
  1333. // padding-bottom
  1334. $styles += '#prev_track, #next_track, #close_audio, #prev_btn, #next_btn' +
  1335. '{ padding-bottom: 0px }';
  1336. $styles += '#details_btn, body.is_dirs_on_top #dir_list tbody tr.sorted:not(:last-of-type)' +
  1337. '{ padding-bottom: 2px; }';
  1338. $styles += '#title, body #content_audio_title td' +
  1339. '{ padding-bottom: 3px;}';
  1340. $styles += '#parents_dir_menu div, #grid_btn .menu li, #dir_list tbody a, #dir_list .details' +
  1341. '{ padding-bottom: 4px; }';
  1342. $styles += '.specimen, .font_grid_item p' +
  1343. '{ padding-bottom: 0.25em; }';
  1344. $styles += '#content_header tr:first-of-type td:first-of-type, #content_header tr:first-of-type td:last-of-type' +
  1345. '{ padding-bottom: 3px; }';
  1346. $styles += '#content_header table' +
  1347. '{ padding-bottom: 5px; }';
  1348. $styles += '#sidebar_header tbody tr:last-of-type td, #parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu + ul li div span, #dir_list thead th, #content_audio td' +
  1349. '{ padding-bottom: 6px; }';
  1350. $styles += '#dir_list thead th.details' +
  1351. '{ padding-bottom: 9px; }';
  1352. $styles += '#content_grid .font_grid_item' +
  1353. '{ padding-bottom: 0.5rem; }';
  1354. $styles += '#content_grid.has_image_grid' +
  1355. '{ padding-bottom: 1rem; }';
  1356. $styles += '#content_image, #content_font div:first-of-type' +
  1357. '{ padding-bottom: 2rem; }';
  1358. // padding-left
  1359. $styles += '#grid_btn .menu, #sidebar_header tbody tr:last-of-type td' +
  1360. '{ padding-left: 0; }';
  1361. $styles += '#dir_list .audio a, #dir_list .video a, #checkbox_div' +
  1362. '{ padding-left: 4px; }';
  1363. $styles += '#parents_dir_menu div, #dir_list td.icon, #dir_list .icon + td.name a, #checkbox_div #parents_dir_menu div, #details_btn, #grid_btn .menu li, #content_header tr:first-of-type td:first-of-type, #content_header tr:first-of-type td:last-of-type, #content_audio td, #prev_track' +
  1364. '{ padding-left: 6px; }';
  1365. $styles += '#parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu + ul li div span' +
  1366. '{ padding-left: 8px; }';
  1367. $styles += '#content_header table, #dir_list td.size, #title, #prev_btn, #next_btn' +
  1368. '{ padding-left: 12px; }';
  1369. $styles += '#dir_list thead th, #dir_list .details, #dir_list td.date' +
  1370. '{ padding-left: 24px; }';
  1371. $styles += '#dir_list tbody a' +
  1372. '{ padding-left: 27px; -webkit-padding-start: 27px; }';
  1373. $styles += '#content_audio_title td' +
  1374. '{ padding-left: 29px; }';
  1375. $styles += '#sort_menu li span, #dir_list thead .name, #default' +
  1376. '{ padding-left: 2em; }';
  1377. $styles += '#content_grid .font_grid_item, #content_image, #content_font div' +
  1378. '{ padding-left: 2.5rem; }';
  1379. //-webkit-padding
  1380. $styles += '#dir_list .icon + td.name a' +
  1381. '{ -webkit-padding-start:1em; }';
  1382.  
  1383. // position
  1384. $styles += '#sidebar_wrapper, #sidebar_header, #sidebar_header tbody tr, #sidebar_header tbody tr:first-of-type td:first-of-type, #sidebar_header tbody tr:last-of-type td, #dir_list, #dir_list thead, #dir_list thead th.checked ,#dir_list tr.ignore a, #dir_list tr.ignore.app a, #dir_list tr.ignore td.details, #shortcuts_menu + ul > li.has_submenu, #content_pane, #close_audio, #content_grid div img, #content_image img:not(.zoom_img)' +
  1385. '{ position: relative; }';
  1386. $styles += '#sidebar ul, #handle, #parent_dir_menu, #toggle_sidebar, body.has_hidden_sidebar #sidebar_wrapper, #dir_list tbody, #dir_list thead th.checked:after, #content_header, #close_audio:after, #content_grid, #content_image, #content_font, #content_pdf, #content_iframe, #scale, #prev_btn, #next_btn, body.use_custom_icons #dir_list tr.file.ignore a.icon::before' +
  1387. '{ position: absolute; }';
  1388.  
  1389. // right
  1390. $styles += '#sidebar ul, #parent_dir_menu, #grid_btn .menu, #dir_list thead, #dir_list tbody, #close_audio:after, #content_header, #content_image, #content_pdf, #content_iframe, #content_grid, #content_font, #next_btn' +
  1391. '{ right: 0; }';
  1392. $styles += '#toggle_sidebar' +
  1393. '{ right: 4px; }';
  1394. $styles += '#handle' +
  1395. '{ right: -4px; }';
  1396. $styles += '#scale' +
  1397. '{ right: 1rem; }';
  1398.  
  1399. // table-layout
  1400. $styles += '#dir_list' +
  1401. '{ table-layout: fixed; }';
  1402.  
  1403. // text-align
  1404. $styles += '#sidebar ul, #dir_list thead, #dir_list tbody, #dir_list tbody .name, #dir_list .details, #content_header tr:first-of-type td:first-of-type, #content_grid, #content_grid .font_grid_item, .specimen' +
  1405. '{ text-align: left; }';
  1406. $styles += '#parent_dir_menu a, #parents_dir_menu, #parents_dir_menu div, #shortcuts_menu div, #content_header, #title, #content_grid .image_grid_item, #content_image, #content_audio_title, #content_audio td, #scale span' +
  1407. '{ text-align: center; }';
  1408. $styles += '#grid_btn .menu li, #dir_list .size, #dir_list .date, #content_header tr:first-of-type td:last-of-type' +
  1409. '{ text-align: right; }';
  1410. $styles += '.lorem' +
  1411. '{ text-align: justify; }';
  1412.  
  1413. // text-decoration
  1414. $styles += 'a, a:hover' +
  1415. '{ text-decoration: none !important; }';
  1416.  
  1417. // text-indent
  1418. $styles += '#shortcuts li' +
  1419. '{ text-indent: 1em; }';
  1420.  
  1421. // text-transform
  1422. $styles += '.font_grid_item p' +
  1423. '{ text-transform: uppercase; }';
  1424.  
  1425. // top
  1426. $styles += '#handle, #parent_dir_menu, #content_container, #content_header, #close_audio:after, #content_image, #content_image img.zoom_img, #content_pdf, #content_iframe, #prev_btn, #next_btn, body.use_custom_icons #dir_list tr.file.ignore a.icon::before' +
  1427. '{ top: 0; }';
  1428. $styles += '#shortcuts_menu + ul > li > ul, body.light_theme #grid_btn .menu' +
  1429. '{ top: -1px !important; }';
  1430. $styles += 'body.has_hidden_sidebar #sidebar_wrapper, #dir_list thead th.checked:after' +
  1431. '{ top: 2px; }';
  1432. $styles += '#toggle_sidebar' +
  1433. '{ top: 4px; }';
  1434. $styles += 'body.dark_theme #grid_btn .menu' +
  1435. '{ top: -7px; }';
  1436. $styles += '#content_image img:not(.zoom_img), .vert_center' +
  1437. '{ top: 50%; }';
  1438.  
  1439. // transform
  1440. $styles += 'body.has_hidden_sidebar #toggle_sidebar, #dir_list thead th.checked:not(.up):after, #next_track, #next_btn' +
  1441. '{ transform:rotate(180deg); }';
  1442. $styles += '#close_audio:after' +
  1443. '{ transform:rotate(45deg); }';
  1444. $styles += '.vert_center' +
  1445. '{ transform:translateY(50%); }';
  1446. $styles += '#content_image img:not(.zoom_img)' +
  1447. '{ transform:translateY(-50%); }';
  1448. $styles += '#content_image img.zoom_img' +
  1449. '{ transform:translateY(0); }';
  1450.  
  1451. // transition
  1452. $styles += '#scale' +
  1453. '{ transition:opacity 1s ease-in-out; }';
  1454.  
  1455. // user-select
  1456. $styles += '#sidebar_header, #content_image img:not(.zoom_img)' +
  1457. '{ user-select:none; -webkit-user-select:none; }';
  1458.  
  1459. // vertical-align
  1460. $styles += '#dir_list .details' +
  1461. '{ vertical-align: top }';
  1462. $styles += '#sidebar_header tbody tr:last-of-type td, #parent_dir_menu a, #parents_dir_menu div, #shortcuts_menu div, #content_header tr:first-of-type td:first-of-type, #content_header tr:first-of-type td:last-of-type, #title, #content_grid .image_grid_item' +
  1463. '{ vertical-align:middle; }';
  1464.  
  1465. // white-space
  1466. $styles += '#sidebar_header tbody tr:last-of-type td, #parents_dir_menu div, #parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #dir_list tbody a, .specimen, .lorem' +
  1467. '{ white-space: normal; }';
  1468. $styles += '#grid_btn .menu li' +
  1469. '{ white-space: pre; }';
  1470. $styles += '#dir_list thead' +
  1471. '{ white-space: pre-wrap; }';
  1472.  
  1473. // width
  1474. $styles += 'html, body, :root, table, #sidebar_header tbody tr:first-of-type td:nth-of-type(even), #parent_dir_menu a, #dir_list thead, #tbody, #tbody tr, #shortcuts_menu + ul > li > ul, #grid_btn .menu li, body.has_hidden_sidebar #content_pane, #content_container, #content_header, #content_pdf, #content_iframe' +
  1475. '{ width: 100%; }';
  1476. $styles += '#content_grid, #content_grid div img, #content_image img:not(.zoom_img)' +
  1477. '{ width: auto; }';
  1478. $styles += '#handle' +
  1479. '{ width: 8px; }';
  1480. $styles += '#toggle_sidebar' +
  1481. '{ width: 16px; }';
  1482. $styles += 'body.use_custom_icons #dir_list tr.file.ignore a.icon::before' +
  1483. '{ width: 20px; }';
  1484. $styles += '#sidebar_header tbody tr:first-of-type td:nth-of-type(odd)' +
  1485. '{ width: 24px; }';
  1486. $styles += '#grid_btn' +
  1487. '{ width: 28px; }';
  1488. $styles += '#dir_list thead th.checked:after, #prev_track, #next_track, #close_audio, #scale span' +
  1489. '{ width: 2rem; }';
  1490. $styles += '#shortcuts_menu div, #checkbox_div' +
  1491. '{ width: 6em; }';
  1492. $styles += '#content_header tr:first-of-type td:first-of-type, #content_header tr:first-of-type td:last-of-type' +
  1493. '{ width: 4em; }';
  1494. $styles += 'body.has_hidden_sidebar #sidebar_wrapper' +
  1495. '{ width: 0 !important; }';
  1496. $styles += '#dir_list .size, #dir_list .kind' +
  1497. '{ width: 16%; }';
  1498. $styles += '#dir_list .date' +
  1499. '{ width: 30%; }';
  1500. $styles += '#content_grid.has_grid .image_grid_item' +
  1501. '{ width: '+ $grid_image_size +'px; }';
  1502. $styles += 'body.has_hidden_sidebar #content_pane, #content_header tr:first-of-type td:nth-of-type(2)' +
  1503. '{ width: 100% !important; }';
  1504. $styles += 'body:not(.has_hidden_sidebar) #sidebar_wrapper' +
  1505. '{ width:'+ $UI_pref_width +'%; }';
  1506. $styles += '#content_pane' +
  1507. '{ width:'+ (100 - $UI_pref_width).toString() +'%; }';
  1508. $styles += '#content_grid .font_grid_item' +
  1509. '{ width:calc(100% - 5rem); }';
  1510. // max-width
  1511. $styles += 'html, body, :root, #content_image img:not(.zoom_img)' +
  1512. '{ max-width: 100%; }';
  1513. $styles += '#content_image img.zoom_img' +
  1514. '{ max-width: none; }';
  1515. $styles += '#sidebar_header tbody tr:first-of-type td:nth-of-type(odd)' +
  1516. '{ max-width: 24px; }';
  1517. $styles += '#content_grid div img' +
  1518. '{ max-width:'+ ($grid_image_size - $grid_image_size/8) +'px; }';
  1519. // min-width
  1520. $styles += 'body:not(.has_hidden_sidebar) #sidebar_header tbody tr:first-of-type td:nth-of-type(odd)' +
  1521. '{ min-width: 24px; }';
  1522. $styles += '#dir_list .size, #dir_list .kind' +
  1523. '{ min-width: 5em; }';
  1524. $styles += '#dir_list td.date' +
  1525. '{ min-width: 12em; }';
  1526. $styles += 'body:not(.has_hidden_sidebar) #dir_list' +
  1527. '{ min-width: 100px; }';
  1528. $styles += 'body:not(.has_hidden_sidebar) #sidebar_wrapper' +
  1529. '{ min-width: 220px; }';
  1530.  
  1531. // will-change
  1532. $styles += '#sidebar_wrapper, #content_pane' +
  1533. '{ will-change: width }';
  1534.  
  1535. // word-break
  1536. $styles += '#content_header td button' +
  1537. '{ word-break: none; }';
  1538. $styles += '#title' +
  1539. '{ word-break: break-word; }';
  1540. $styles += '#content_font' +
  1541. '{ word-break: break-all; }';
  1542. $styles += '.lorem' +
  1543. '{ word-break: normal; }';
  1544.  
  1545. // z-index
  1546. $styles += '#content_grid' +
  1547. '{ z-index: -1; }';
  1548. $styles += '#content_pane.has_grid #content_grid' +
  1549. '{ z-index: 1; }';
  1550. $styles += '#scale, #prev_btn, #next_btn' +
  1551. '{ z-index: 10; }';
  1552. $styles += '#parents_dir_menu + ul, #content_header' +
  1553. '{ z-index: 20; }';
  1554. $styles += '#handle' +
  1555. '{ z-index: 1000; }';
  1556. $styles += '#sidebar_header' +
  1557. '{ z-index: 2000; }';
  1558. $styles += '#shortcuts_menu + ul, #toggle_sidebar' +
  1559. '{ z-index: 9999; }';
  1560. $styles += 'body.has_hidden_sidebar #sidebar_header' +
  1561. '{ z-index:unset; }';
  1562.  
  1563. // Gecko Styles:
  1564. var $gecko_styles = '';
  1565.  
  1566. $gecko_styles += 'html, body.is_gecko { border: solid 1px gray !important; }';
  1567. $gecko_styles += 'body.is_gecko button { padding:0; }';
  1568. $gecko_styles += 'body.is_gecko #grid_btn .menu { top:-7px; left:-120px; }';
  1569. $gecko_styles += 'body.is_gecko thead {font-size:100%;}';
  1570. $gecko_styles += 'body.is_gecko #dir_list th, body.is_gecko #dir_list td {width:auto;}';
  1571. $gecko_styles += 'body.is_gecko #dir_list .dir::before {position:absolute;}';
  1572. $gecko_styles += 'body.is_gecko.use_default_icons:not(.is_converted_list) #dir_list .file .name .icon { padding-left:4px; background:none; }';
  1573. $gecko_styles += 'body.is_gecko.use_default_icons #dir_list .file .name .icon img { margin-right:6px; height:14px; }';
  1574. $gecko_styles += 'body.is_gecko #tbody > tr > td:not(:first-of-type) { float:left }';
  1575. $gecko_styles += 'body.is_gecko #content_audio_title td { padding-top: 6px; }';
  1576. $gecko_styles += 'body.is_gecko #content_audio_title td { padding-bottom: 0; }';
  1577. $gecko_styles += 'body.is_gecko #prev_track { padding-left: 16px; }';
  1578. $gecko_styles += 'body.is_gecko #close_audio { background-color: #252525; }';
  1579. $gecko_styles += 'body.is_gecko #close_audio:after { filter:invert(100%); opacity:0.5; }';
  1580. $gecko_styles += 'body.is_gecko #close_audio:hover:after { opacity:0.75; }';
  1581. $gecko_styles += 'body.is_gecko.dark_theme #prev_track, body.is_gecko.dark_theme #next_track { background: #252525 '+ $next_track_arrow_gecko +' center no-repeat; }';
  1582. $gecko_styles += 'body.is_gecko #prev_track:hover, body.is_gecko #next_track:hover { background: #252525 '+ $next_track_arrow_gecko_hover +' center no-repeat; }';
  1583. $gecko_styles += 'body.is_gecko #content_title td:first-of-type, body.is_gecko #content_title td:last-of-type { padding-top:8px; }';
  1584.  
  1585. // ADD STYLES
  1586. const $font_preview_styles = document.createElement('style');
  1587. const $custom_styles = document.createElement("style");
  1588. const $custom_gecko_styles = document.createElement("style");
  1589.  
  1590. const $highlightjs_link = document.createElement('link');
  1591. $highlightjs_link.setAttribute('rel','stylesheet') ;
  1592. $highlightjs_link.setAttribute('href','http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.14.2/styles/default.min.css') ;
  1593. document.head.appendChild($highlightjs_link);
  1594.  
  1595. function addStyles() {
  1596. // for font previews
  1597. $font_preview_styles.appendChild(document.createTextNode(""));
  1598. document.head.append($font_preview_styles);
  1599. var $font_styles = '';
  1600. $font_preview_styles.append( $font_styles );
  1601. // basic UI styles
  1602. $custom_styles.appendChild(document.createTextNode(""));
  1603. $custom_styles.append($styles);
  1604. document.head.appendChild($custom_styles);
  1605. // Gecko styles
  1606. if ( $userAgent.indexOf('Firefox') > -1 ) {
  1607. $custom_gecko_styles.appendChild(document.createTextNode(""));
  1608. $custom_gecko_styles.append($gecko_styles);
  1609. document.head.appendChild($custom_gecko_styles);
  1610. }
  1611. }
  1612. addStyles();
  1613. // ***** END STYLES ***** //
  1614.  
  1615. // ***** BUILD MENUS ***** //
  1616. var $paths_arr = [];
  1617. const parentLinksArr = function() { // create array of parent links from $location w/o query string
  1618. let $path = $location; // https://www.example.com/path/to/my/dir/
  1619. while ( $path.lastIndexOf('/') > 0 && !$path.endsWith(':/') ) {
  1620. $path = $path.slice( 0,$path.lastIndexOf('/') );
  1621. $paths_arr.push($path);
  1622. }
  1623. $paths_arr.pop(); // remove last element
  1624. return $paths_arr;
  1625. };
  1626. parentLinksArr();
  1627.  
  1628. // MENUS: Create menu list items
  1629. var $menu_item;
  1630. const menuItems = function(x,y,i,b) { // (link, name, count, boolean)
  1631. let $qstr = '/?';
  1632. if ( b === true ) { // don't include query string in link
  1633. $qstr = '';
  1634. $query_prefs = '';
  1635. }
  1636. $menu_item = '<li><a href="'+ x[i] + $qstr + $query_prefs +'">' + y[i] + '/</a></li>';
  1637. return $menu_item;
  1638. };
  1639. // Parent directory link setup
  1640. var $parent_dir_link;
  1641. function setParentLink() {
  1642. let $query_prefs = new URLSearchParams( window.location.search );
  1643. $query_prefs.delete('file');
  1644.  
  1645. $UI_pref_history_arr = $query_prefs.get('history') !== null ? $query_prefs.get('history').split(' ') : [];
  1646.  
  1647. if ( $UI_pref_history_arr.length == 1 ) {
  1648. $query_prefs.set('selected',$UI_pref_history_arr.shift(0));
  1649. $query_prefs.delete('history');
  1650. } else if ( $UI_pref_history_arr.length > 1 ) {
  1651. $query_prefs.set('selected',$UI_pref_history_arr.shift(0));
  1652. $query_prefs.set('history',$UI_pref_history_arr.join('+'));
  1653. } else {
  1654. $query_prefs.delete('selected');
  1655. }
  1656. let $qstr = $query_prefs.toString().length === 0 ? '' : '?';
  1657. $query_prefs.sort();
  1658. $parent_dir_link = $location_arr.slice(0,-2).join('/') + '/' + $qstr + decodeURIComponent($query_prefs);
  1659.  
  1660. $parent_dir_menu.find('a').attr( 'href', $parent_dir_link );
  1661. }
  1662. setParentLink();
  1663. // Click parent directory menu button
  1664. $parent_dir_menu.on('click','a',function(e) {
  1665. e.preventDefault();
  1666. setParentLink(); // update link in case query string has changed
  1667. $('#parent_dir_menu').find('a').attr( 'href', $parent_dir_link );
  1668. window.location = $parent_dir_link;
  1669. });
  1670.  
  1671. // MENUS: Directory Parents Links
  1672. const parents_dir_menu_arr = function() {
  1673. var $parents_dir_menu_items = [];
  1674. // make array of directory parents menu items
  1675. for ( let i = 1, n = $paths_arr.length + 1; i < n; i+=1 ) {
  1676. $parents_dir_menu_items[i] = menuItems( $paths_arr, $paths_arr, i, false);
  1677. }
  1678. $parents_dir_menu_items.pop(); // remove current directory
  1679.  
  1680. if ( $parents_dir_menu_items[1] !== undefined ) {
  1681. let str = $parents_dir_menu_items[1].slice($parents_dir_menu_items[1].lastIndexOf('?'),$parents_dir_menu_items[1].lastIndexOf('">'));
  1682. for ( let i = 1; i < $parents_dir_menu_items.length; i+=1 ) { // recursively update selected and history query str
  1683. str = str.replace(/selected=\d+&*/,'').replace(/history=(\d+)$/,'selected=$1').replace(/history=(\d+)\+/,'selected=$1&history=').replace(/&$/,'');
  1684. $parents_dir_menu_items[i] = $parents_dir_menu_items[i].replace(/\?.+?">/,str +'">');
  1685. }
  1686. }
  1687. // cleanup links
  1688. $parents_dir_menu_items = $parents_dir_menu_items.join('')
  1689. .replace(/%20/g,' ').replace(/file=.+?&/g,'&').replace(/file=.+?/g,'').replace(/>\/(?!\/)/g,'>').replace(/>\/\/\/\/</,'>/<').replace(/>\/\/\/</,'>/<');
  1690. return $parents_dir_menu_items;
  1691. };
  1692.  
  1693. // MENUS: User Shortcuts
  1694. var $menu_items = [];
  1695. var $shortcuts_links_arr = [];
  1696.  
  1697. const shortcutMenus = function() {
  1698. const $shortcuts = $settings.shortcuts;
  1699.  
  1700. if ( $shortcuts.length > 0 ) {
  1701. for ( let i = 0; i < $shortcuts.length; i+=1 ) {
  1702. var $links_arr = [];
  1703. var $links = $shortcuts[i].links;
  1704. // make array of links
  1705. for ( let j = 0; j < $links.length; j+=1 ) {
  1706. if ( !$links[j].link.endsWith('/') ) {
  1707. $links[j].link = $links[j].link.slice(0,$links[j].link.lastIndexOf('/') + 1) + '?file=' + $links[j].link.slice($links[j].link.lastIndexOf('/') + 1) ;
  1708. }
  1709. $shortcuts_links_arr.push($links[j].link);
  1710. $links_arr[j] = '<li><a href="'+ $links[j].link +'">' + $links[j].link_name + '</a></li>';
  1711. }
  1712. var $links_arr_str = $links_arr.join('');
  1713. $menu_items[i] = '<li class="has_submenu"><a>'+ $shortcuts[i].menu_title +'</a><ul>'+ $links_arr_str +'</ul></li>';
  1714. }
  1715. $menu_items = $menu_items.join('');
  1716. }
  1717. return ($shortcuts_links_arr,$menu_items);
  1718. };
  1719. shortcutMenus();
  1720.  
  1721. // MENUS: Other menu items
  1722. var $sort_by = $('<li id="sort_by" class="has_submenu rule"><span>Sort by&hellip;</span><ul id="sort_menu"><li><span>Name</span></li><li id="sort_by_size"><span>Size</span></li><li id="sort_by_date"><span>Date</span></li><li><span>Kind</span></li><li><span>Extension</span></li><li><span>Default</span></li></ul>');
  1723. const $autoload_media = $('<li id="autoload_media" class="rule"><span id="autoload_media_menu">Autoload Media</span></li>');
  1724. const $toggle_dark_theme = $('<li id="toggle_dark_theme_menu_item" class="checked"><div><span id="dark_theme">Dark Theme</span><span id="light_theme">Light Theme</span></div></li>');
  1725. const $toggle_alt_bckgrd = $('<li id="toggle_alternate_background_menu_item" class="rule"><span id="alternate_background">Alternate Backgrounds</span></li>');
  1726. // const $text_editing = $('<li id="text_editing" class="has_submenu rule"><span>Text Editing</span><ul id="text_editing_menu"><li id="split_view_menu_item"><span id="split_view">Split View</span></li><li id="preview_text_menu_item" class="rule"><span id="preview_text">Preview Text Files</span><span id="edit_text">Edit Text Files</span></li></ul>');
  1727. const $default_settings = $('<li><a href="#" id="default_settings" title="Delete URL query string and reload page.">Default User Settings</a></li>');
  1728. const $export_settings = $('<li class="rule"><a href="#" id="export_settings" title="Export user settings to text file.">Export User Settings</a></li>');
  1729. const $contact_link = $('<li><a id="contact" href="mailto:mshroud@vivaldi.net">Contact</a></li>');
  1730. const $donate_link = $('<li><a id="donate" href="https://paypal.me/mschrauzer" target="_blank">Donate</a></li>');
  1731.  
  1732. function assembleMenus() { // called by buildUI();
  1733. $parents_dir_menu.siblings('ul').append( parents_dir_menu_arr() );
  1734. $shortcuts_menu.siblings('ul').append( $menu_items, $sort_by, $toggle_dark_theme, $toggle_alt_bckgrd, $autoload_media, $default_settings, $export_settings, $contact_link, $donate_link );
  1735. }
  1736. assembleMenus();
  1737. // ***** END BUILD MENUS ***** //
  1738.  
  1739. // ***** DIRECTORY LIST Setup ***** //
  1740. // CONVERT INDEX PAGES TO DEFAULT (Chrome) STRUCTURE
  1741.  
  1742. // Convert matched date and size strings to default size and date format, calculate data-values for sorting
  1743. var yr, mo, dd, pref;
  1744. var factor = {k:1, kb:1, m:1000, mb:1000, g:1000000, gb:1000000, t:1000000000,tb:1000000000}; // for file size conversion
  1745. var sizeCalc = function(x,y) { // x = number, y = byte size
  1746. y === '' ? y = 'k' : y = y;
  1747. return x == '-' ? 0 : x * factor[ y.toString().toLowerCase() ]; // convert byte size to multiplication factor
  1748. };
  1749. var sizeAndDate = function(match,p1,p2,p3,p4,p5,p6,offset,string) {
  1750. p1.toString().length == 4 ? (yr = p1, dd = p3) : (yr = p3, dd = p1);
  1751. p2.toString().length == 3 ? mo = "JanFebMarAprMayJunJulAugSepOctNovDec".indexOf(p2)/3 + 1 : mo = p2; // convert month into number, or use number
  1752. p6 = undefined|'' ? p6 = 1 : p6 = p6;
  1753. mo.toString().length == 1 ? pref = 0 : pref = null; // add a zero before single digit months (i.e., multiply year by 10)
  1754. return '<td class="size details" data-value="'+ sizeCalc(p5,p6) +'">' + [p5,p6].join('') + '</td><td class="date details" data-value="' + [yr, pref, mo, dd, p4.replace(/:/, '') ].join('') +'">'+ [yr, mo, dd].join('-') +' '+ [p4].join(':') +'</td></tr>\n';
  1755. };
  1756. // Standardize directory lists
  1757. function convertDirList() {
  1758. var $list_items;
  1759. // AMPPS index pages
  1760. let $protocol = window.location.protocol;
  1761. if ( $protocol.indexOf('localhost') > -1 && $body.find('> title:contains(AMPPS)').length ) {
  1762. $dir_list = $body.find('> center > div > table');
  1763. $dir_list.remove();
  1764. $body.addClass('is_ampps').empty().append($dir_list);
  1765. }
  1766. // Pre-type
  1767. if ( $protocol !== 'file:' && $body.find('> pre').length > 0 ) {
  1768. $list_items = $body.find('> pre').html();
  1769. $body.addClass('is_converted_pre').find('> pre').remove();
  1770.  
  1771. $list_items = $list_items
  1772. .replace(/^.+?\n/, '') // delete header row
  1773. .replace(/^.+?Parent Directory.+?\n/gm, '\n') // delete parent directory link
  1774. .replace(/^\s*<a.+?>\.\.\/<\/a>\s*$/gm, '') // delete parent directory link (list type)
  1775. .replace(/^\s*<img .+?>\s*/gm, '') // delete all images (icons)
  1776. .replace(/^\s*<address>.+?<\/address>\s*/,'') // delete address element
  1777. .replace(/^\s*<a href="(.+?)">(.+?)<\/a>\s*/gm, '<tr><td data-value=""><a class="icon" href="$1">$2</a></td>') // create name cell
  1778. .replace(/(\d{2}|\d{4})[\/-](\d{2}|\w{3})[\/-](\d{2}|\d{4})\s+?([\d:]+)\s*([\.\d]+)([A-z]{0,2})\s*$/gm, sizeAndDate); // dates (search dd-mmm-yyyy hh:mm) and sizes
  1779. }
  1780. // List-type
  1781. if ( $protocol !== 'file:' && $body.find('> ul').length > 0 ) {
  1782. $list_items = $body.find('> ul').html();
  1783. $body.addClass('is_converted_list').find('> ul').remove();
  1784.  
  1785. if ( $list_items.indexOf('</a></li>') != -1 ) {
  1786. $list_items = $list_items.replace(/<li>\s*/g,'<tr><td data-value="">').replace(/\s*<\/li>/g, '</td></tr>\n');
  1787. $list_items = $list_items.replace(/data-value=""><a href="(.+?)">\s+(.+?)<\/a>/g, 'data-value=""><a class="icon" href="$1">$2<\/a>');
  1788. $list_items = $list_items.replace(/<tr.+?Parent Directory.+?<\/tr>/,'');
  1789. }
  1790. }
  1791. // Table-type
  1792. if ( $protocol !== 'file:' && $body.find('> table').length > 0 ) {
  1793. $list_items = $body.find('> table:last-of-type tbody').length > 0 ? $body.find('> table:last-of-type tbody').html() : $body.find('> table:last-of-type');
  1794. $body.addClass('is_converted_table').find('> table').remove();
  1795.  
  1796. $list_items = $list_items
  1797. // .replace(/<img .+?>/g, '') // delete all images (icons)
  1798. .replace(/<tr><th.+?\/th><\/tr>/g, '') // delete empty tr
  1799. .replace(/<tr><td.+?Parent Dir.+?<\/td><\/tr>/, '') // delete empty tr
  1800. .replace(/(?:<th>|<td>|<td \w+="\w+">)(?:&nbsp;|\s*)(?:<\/th>|<\/td>)/g, '') // delete empty th or td
  1801. .replace(/<td>\s*<a href="(.+?)">(.+?)<\/a>\s*<\/td>/g, '<td class="name" data-value=""><a class="icon" href="$1">$2</a></td>')
  1802. .replace(/<tr><td.*?><img (.+?)><\/td><td (.+?)><a (.+?)>(.+?)<\/a>/g, '<tr><td $2><a $3><img $1>$4</a>') // move default image to .name td
  1803. .replace(/(?:<td>|<td \w+="\w+">)(\d{4}|\d{2})-(\d{2}|\w{3})-(\d{4}|\d{2})\s*([\d:-]*)\s*<\/td>(?:<td>|<td \w+="\w+">)\s*([-\.\d]*)([A-z]*)\s*<\/td><\/tr>/g, sizeAndDate)
  1804. .replace(/^\s*<address>.+?<\/address>\s*$/,''); // delete address line
  1805. }
  1806. // Gecko-style local index pages
  1807. if ( $protocol === 'file:' && $userAgent.indexOf('Firefox') > -1 ) {
  1808. $body.find('> table').find('> thead').find('th[colspan="2"]').attr('colspan',1);
  1809. $list_items = $body.find('> table').find('> tbody').html();
  1810. $body.find('> table').remove();
  1811.  
  1812. $list_items = $list_items
  1813. .replace(/<table class="ellipsis"><tbody><tr><td>/g,'')
  1814. .replace(/<\/a><\/td><\/tr><\/tbody><\/table>/g,'')
  1815. .replace(/sortable-data="\d+?/g,'data-value="')
  1816. .replace(/<td data-value="(\d+)">([\d-]+)<\/td>\s*<td>(.+?)<\/td>/g,'<td data-value="$1">$2, $3</td>')
  1817. .replace(/<td(.+?)><a/g,'<td class="name" $1><a class="icon"');
  1818. }
  1819. // Default Chrome type
  1820. if ( $protocol === 'file:' && $body.find('> table').length > 0 && $userAgent.indexOf('Firefox') < 0) {
  1821. $list_items = $body.find('> table tbody').html();
  1822. $body.addClass('is_default_table').find('> table').remove();
  1823. }
  1824. $dir_list_body.append($list_items);
  1825. }
  1826. convertDirList();
  1827. // ***** END CONVERT INDEX PAGES ***** //
  1828.  
  1829. // Append directory list
  1830.  
  1831. var $dir_list_row = $dir_list_body.find('> tr');
  1832. var $dir_list_item_name = $dir_list_row.find('a').parent('td');
  1833. var $dir_list_details = $dir_list_item_name.nextAll();
  1834. $dir_list_details.addClass('details');
  1835.  
  1836. // ***** END CONVERT INDEX PAGES ***** //
  1837.  
  1838. // ***** PREPARE DIR LIST ***** //
  1839.  
  1840. var $this_link;
  1841. var $this_text;
  1842. var $this_ext;
  1843.  
  1844. // Get dir_list item row
  1845. var thisRow = function(x) {
  1846. return $(x).closest('#dir_list > tbody > tr').length > 0 ? $(x).closest('#dir_list > tbody > tr') : $(x).closest('#dir_list > li');
  1847. };
  1848. // Get row link
  1849. var thisLink = function(x) {
  1850. return $(x).find('a').length > 0 ? $(x).find('a').attr('href') : $(x).attr('href');
  1851. };
  1852. // Get row name
  1853. var thisText = function(x) {
  1854. return $(x).find('a').length > 0 ? decodeURIComponentSafe( $(x).find('a').text().toLocaleLowerCase() ) : decodeURIComponentSafe( $(x).text().toLocaleLowerCase() );
  1855. };
  1856. // get row text
  1857. var thisExt = function(x) {
  1858. let $this_name = thisText(x);
  1859. // return $this_link.endsWith('app/') ? 'app' : $this_link.endsWith('/') ? '/' : $this_link.lastIndexOf('.') === -1 ? undefined : $this_link.toLowerCase().slice( $this_link.lastIndexOf('.') + 1 );
  1860. return $this_name.endsWith('app/') ? 'app' : $this_name.endsWith('/') ? '/' : $this_name.lastIndexOf('.') === -1 ? undefined : $this_name.toLowerCase().slice( $this_name.lastIndexOf('.') + 1 );
  1861. };
  1862. // Normalize row name data-value
  1863. function stringNormalize(str) {
  1864. return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "").replace(/ª/g,'a').replace(/[æÆ]/g,'ae').replace(/©/g,'c').replace(/[ÐÐ]/g,'d').replace(/fi/g,'fi').replace(/fl/g,'fl').replace(/[œŒ]/g,'oe').replace(/[ºøØ]/g,'o').replace(/π∏/g,'p').replace(/®/g,'r').replace(/ß/g,'ss').replace(/™/g,'tm').replace(/[‘’]/g,'\'').replace(/[“”«»]/g,'"').replace(/[†‡§]/g,'*');
  1865. }
  1866. // Dir_List: Classify items
  1867. function classifyDirListItems() {
  1868. $dir_list_row.each(function() {
  1869. let row = {};
  1870. $this_link = thisLink(this);
  1871. $this_text = thisText(this).endsWith('/') ? thisText(this).slice(0,-1) : thisText(this); // don't include '/'
  1872. $this_ext = thisExt(this);
  1873. // add classes and data to row cells
  1874. $(this).attr('id',$(this).index()).find('td').first().addClass('name').attr( 'data-value',stringNormalize( $this_text )).next('td:not(.kind)').attr('class','size').next('td:not(.kind)').attr('class','date'); // add tr ID and td.name data-value
  1875. // add dir or file classes
  1876. if ( $this_ext === '/' ) { // add tr data-ext
  1877. $(this).addClass('dir').attr('data-ext','dir');
  1878. } else {
  1879. $(this).addClass('file').attr('data-ext',$this_ext);
  1880. }
  1881. // add tr invisible class
  1882. if ( $this_text.startsWith('.') ) {
  1883. $(this).addClass('invisible');
  1884. }
  1885. // add kind and ext detail columns
  1886. $(this).append('<td class="detailsColumn details kind" data-value="File">File</td>')
  1887. .append('<td class="detailsColumn details ext" data-value="'+ $this_ext +'">&nbsp;</td>');
  1888. if ( $this_ext === undefined ) {
  1889. $(this).addClass('code').attr('data-type','Code').attr('data-kind','code').find('td.kind').attr('data-value','Code').text('Code');
  1890. return;
  1891. } else if ( !JSON.stringify($row_types).includes($this_ext) ) { // else classify as "other" if extension is not in $row_types.
  1892. $(this).addClass('other').attr('data-type','Other').attr('data-kind','other').find('td.kind').attr('data-value','other').text('Other');
  1893. }
  1894. for ( let key in $row_types ) { // Categorize rows by listed supported file types
  1895. if ( $row_types[key].includes($this_ext) ) {
  1896. let $display_key = key.charAt(0).toUpperCase() + key.slice(1); // make display name for file type
  1897. let kind = $display_key === undefined ? 'File' : $display_key;
  1898. $(this).addClass(key).addClass($this_ext).attr('data-type',$display_key).attr('data-kind',kind.toLowerCase() ).find('td.kind').attr('data-value',kind).text(kind); // add kind and ext classes, data-type, data-kind
  1899.  
  1900. if ( key === 'audio' || key === 'video' ) { // media classes and prepend checkbox
  1901. $(this).addClass('media').find('a').prepend('<input type="checkbox" tabindex="-1" checked="true" />');
  1902. }
  1903. }
  1904. }
  1905. // For ignored and excluded items
  1906. for ( let key in $row_settings ) {
  1907. if ( $row_settings[key].includes($this_ext) ) {
  1908. $(this).addClass(key);
  1909. }
  1910. if ( $row_settings[key].includes($this_ext) && key == 'ignore' ) {
  1911. }
  1912. }
  1913.  
  1914. }); // end classify dir_list items
  1915. }
  1916. classifyDirListItems();
  1917.  
  1918. // ***** END DIR_LIST SETUP ***** //
  1919.  
  1920. // ***** UI SETUP ***** //
  1921.  
  1922. // Add body classes
  1923. function setupUI() {
  1924. if ( $dir_list.find('.audio').length > 0 ) {
  1925. $body.add($dir_list).addClass('has_audio has_media');
  1926. }
  1927. if ( $dir_list.find('.font').length > 0 ) {
  1928. $body.addClass('has_fonts');
  1929. }
  1930. if ( $dir_list.find('.image').length > 0 ) {
  1931. $body.addClass('has_images');
  1932. }
  1933. if ( $dir_list.find('.video').length > 0 ) {
  1934. $body.add($dir_list).addClass('has_video has_media');
  1935. }
  1936. setQuery('autoload_media',$UI_pref_autoload_media);
  1937. if ( $UI_pref_autoload_media === true ) {
  1938. $body.addClass('autoload_media');
  1939. $autoload_media.addClass('checked');
  1940. } else {
  1941. $body.removeClass('autoload_media');
  1942. $autoload_media.removeClass('checked');
  1943. }
  1944. if ( $settings.autoload_index_files === true ) {
  1945. $body.addClass('autoload_index_files');
  1946. }
  1947. if ( $settings.use_custom_icons === true ) {
  1948. $body.addClass('use_custom_icons')
  1949. } else {
  1950. $body.addClass('use_default_icons');
  1951. }
  1952. if ( $settings.hide_ignored_files === true ) {
  1953. $body.addClass('hide_ignored');
  1954. }
  1955. if ( navigator.platform.indexOf('Win') > -1 || $location.indexOf('file:') < 0 ) {
  1956. $inv_checkbox.hide();
  1957. } else if ( $settings.hide_invisibles === true ) {
  1958. $inv_checkbox.find('input').prop('checked',true);
  1959. $body.addClass('hide_invisibles').removeClass('show_invisibles')
  1960. } else {
  1961. $inv_checkbox.find('input').prop('checked',false);
  1962. $body.addClass('show_invisibles').removeClass('hide_invisibles')
  1963. }
  1964. setQuery('alternate_background',$UI_pref_background);
  1965. if ( $UI_pref_background === true ) {
  1966. $body.addClass('alternate_background');
  1967. $toggle_alt_bckgrd.addClass('checked');
  1968. } else {
  1969. $body.removeClass('alternate_background');
  1970. $toggle_alt_bckgrd.removeClass('checked');
  1971. }
  1972. if ( $UI_pref_details === false ) {
  1973. $dir_list.addClass('show_details');
  1974. $dir_list_body.css({'top':$dir_list_head.height() + 1 +'px'});
  1975. $details_btn.find('span').first().hide().next().show();
  1976. }
  1977. if ( $UI_pref_theme === true ) {
  1978. $body.addClass('dark_theme');
  1979. } else {
  1980. $body.addClass('light_theme');
  1981. }
  1982. if ( $userAgent.indexOf('Chrome') > 0 ) { $body.addClass('is_chrome'); }
  1983. if ( $userAgent.indexOf('Firefox') > 0 ) { $body.addClass('is_gecko'); }
  1984. }
  1985. setupUI();
  1986.  
  1987. // Build UI
  1988. function buildUI() {
  1989. $dir_list.appendTo($sidebar);
  1990.  
  1991. var $main_content = $('<table id="main_content"><tbody><tr></tr></tbody></table>');
  1992. $main_content.find('tr').append( $sidebar_wrapper, $content_pane );
  1993.  
  1994. $body.empty().prepend($main_content);
  1995.  
  1996. if ( $body.hasClass('is_converted_list') ) {
  1997. $('#sort_by_size, #sort_by_date, #size, #date').addClass('disabled');
  1998. }
  1999. if ( $body.hasClass('is_converted_table') || $body.hasClass('is_converted_pre') ) {
  2000. if ( $dir_list.find('td.size').length < 1 ) {
  2001. $('#sort_by_size,th#size').addClass('disabled');
  2002. }
  2003. if ( $dir_list.find('td.date').length < 1 ) {
  2004. $('#sort_by_date,th#date').addClass('disabled');
  2005. }
  2006. }
  2007. }
  2008. buildUI();
  2009.  
  2010. // ***** BASIC UI FUNCTIONS ***** //
  2011. // Show Menus
  2012. function showMenus(el) {
  2013. var $position = $(el).position();
  2014. $(el).find('> ul').css({'top':$position.top + $(el).innerHeight() + 'px'}).toggle().parent('td').siblings('td').find('.menu').hide();
  2015. }
  2016. $parents_dir_menu.add($shortcuts_menu).parent('td').on('click',function(e) {
  2017. e.stopPropagation();
  2018. showMenus(this);
  2019. });
  2020. // Hide Menus
  2021. function hideMenus() {
  2022. $('.menu').hide();
  2023. }
  2024. $(document).on('click', hideMenus );
  2025. // Toggle Invisibles
  2026. function toggleInvisibles() {
  2027. $body.toggleClass('hide_invisibles').toggleClass('show_invisibles');
  2028. }
  2029. $inv_checkbox.on('click','input', toggleInvisibles );
  2030. // Toggle Autoload Media
  2031. function toggleAutoloadMedia(e) {
  2032. e.preventDefault();
  2033. $body.toggleClass('autoload_media');
  2034. toggleQuery('autoload_media');
  2035. $autoload_media.toggleClass('checked');
  2036. }
  2037. $autoload_media.on('click', toggleAutoloadMedia );
  2038. // Toggle Dark Theme
  2039. function toggleDarkMode(e) {
  2040. e.preventDefault();
  2041. $body.toggleClass('dark_theme').toggleClass('light_theme');
  2042. toggleQuery('dark_theme');
  2043. }
  2044. $toggle_dark_theme.on('click', toggleDarkMode );
  2045. // Toggle Alternating Row Background
  2046. function toggleAlternateBackground(e) {
  2047. e.preventDefault();
  2048. $body.toggleClass('alternate_background');
  2049. toggleQuery('alternate_background');
  2050. $toggle_alt_bckgrd.toggleClass('checked');
  2051. }
  2052. $toggle_alt_bckgrd.on('click', toggleAlternateBackground );
  2053. // Toggle Details
  2054. function toggleDetails() {
  2055. $dir_list.toggleClass('show_details');
  2056. $dir_list_body.css({'top':$dir_list_head.height() + 1 +'px'});
  2057. $details_btn.find('span').toggle();
  2058. toggleQuery('hide_details');
  2059. }
  2060. $details_btn.on('click', toggleDetails );
  2061. // Show/Hide Sidebar
  2062. function toggleSidebar() {
  2063. $body.toggleClass('has_hidden_sidebar');
  2064. setContentHeight();
  2065. }
  2066. $toggle_sidebar.on('click', function() {
  2067. toggleSidebar();
  2068. scrollThis('dir_list','selected',true); // true = instant scroll
  2069. });
  2070. // Default settings: remove queries;
  2071. function defaultSettings() {
  2072. $location = window.location.href;
  2073. $location = $location.slice(0,$location.lastIndexOf('?'));
  2074. window.location.assign($location);
  2075. }
  2076. $default_settings.on('click', function() {
  2077. if (window.confirm( "Are you sure you want to reset all your temporary UI settings to the defaults in your user_settings?" ) ) {
  2078. defaultSettings();
  2079. }
  2080. });
  2081.  
  2082. //***** UI SETUP *****//
  2083.  
  2084. // Set Content Height
  2085. function setContentHeight() {
  2086. let $window_height = window.innerHeight;
  2087. let $dir_list_head_height = $dir_list_head.length ? $dir_list_head.height() : 0;
  2088. let $content_header_height = $content_header.outerHeight();
  2089. $dir_list.add($dir_list_wrapper).css({'height':$window_height - $sidebar_header.outerHeight() });
  2090. $dir_list.find($dir_list_body).css({'top': $dir_list_head_height });
  2091. $content_image.add($prev_btn).add($next_btn).css({'top':$content_header_height });
  2092. $content_pane.hasClass('has_image') ? $content_image.find('img').css({'max-height':$content_image.innerHeight() - 52 + 'px'}) : null;
  2093. $content_grid.add($content_pdf).add($content_iframe).add($content_font).css({'height':$window_height - $content_header_height,'top':$content_header_height });
  2094. $content_scale.css({'top':$content_header_height + 13 });
  2095. $('#prev_track, #next_track, #close_audio').css({'height':$audio.height() });
  2096. }
  2097. window.addEventListener('resize', function(e) {
  2098. setContentHeight();
  2099. });
  2100.  
  2101. // Set Content Title
  2102. const $title = $content_pane.find('#title');
  2103.  
  2104. function setContentTitle() {
  2105. let $title_text = '';
  2106. if ( $('.playing').hasClass('audio') ) { // set audio player title
  2107. $content_audio_title.find('td').empty().append( $('.playing').find('td.name a').text() );
  2108. }
  2109. if ( $content_pane.hasClass('has_grid') ) { // set main title for other content
  2110. $title_text = $current_dir_name;
  2111. } else {
  2112. $title_text = $title_text + $('.selected:not(.audio)').find('td.name a').text(); // Assemble title
  2113. }
  2114. $title.empty().text( $title_text ); // Add file name
  2115. if ( $('.selected').hasClass('image') && !$content_pane.hasClass('has_grid') ) {
  2116. setDimensions($('.selected.image'));
  2117. }
  2118. }
  2119. // Get Image Dimensions
  2120. function getDimensions(link, callback) {
  2121. var img = new Image();
  2122. img.src = link;
  2123. img.onload = function() { callback( this.width, this.height ); };
  2124. }
  2125. function setDimensions(row) {
  2126. getDimensions( thisLink(row), function( width, height ) {
  2127. $title.append(' <span style="text-transform:lowercase;">(' + width + 'px &times; ' + height + 'px</span>)' );
  2128. });
  2129. }
  2130. // Scroll Selected Items
  2131. function scrollThis(elID,className,bool) {
  2132. let $behavior = 'smooth';
  2133. bool === undefined ? null : $behavior = 'instant';
  2134. var $block = $userAgent.indexOf('Firefox') > -1 ? $block = 'start' : $block = 'nearest';
  2135. if ( document.getElementsByClassName(className).length > 0 ) {
  2136. document.getElementById(elID).getElementsByClassName(className)[0].scrollIntoView({ behavior:$behavior, block:$block, inline:'nearest' });
  2137. }
  2138. }
  2139.  
  2140. // ***** SORTING ***** //
  2141. var $sort_all = $dir_list_body.find('tr');
  2142. var $sort_dirs = $dir_list_body.find('tr.dir').not('.app');
  2143. var $sort_files = $dir_list_body.find('tr').not('.dir').add('.app');
  2144. var currentIndex, prevIndex;
  2145. var $default_index = $('#thead').find('#default').index();
  2146. var $sort_order = 1;
  2147.  
  2148. var dataName = function(x) {
  2149. return $(x).find('td').eq(0).data('value').toString();
  2150. };
  2151. var dataData = function(x,index) {
  2152. return $(x).find('td').eq(index).attr('data-value') === undefined ? '' : $(x).find('td').eq(index).attr('data-value').toString();
  2153. };
  2154. // Sort comparison functions
  2155. var sortByName = function(x,y,sortDirection) {
  2156. return sortDirection === 1 ? dataName(x).localeCompare(dataName(y)) : dataName(y).localeCompare(dataName(x));
  2157. };
  2158. var sortByData = function(x,y,index,sortDirection) { // sort by data then by name
  2159. if ( sortDirection === 1 ) {
  2160. return dataData(x,index).localeCompare(dataData(y,index), undefined, { numeric:true }) === 0 ? dataName(x).localeCompare(dataName(y)) : dataData(x,index).localeCompare(dataData(y,index), undefined, { numeric:true });
  2161. } else {
  2162. return dataData(y,index).localeCompare(dataData(x,index), undefined, { numeric:true }) === 0 ? dataName(y).localeCompare(dataName(x)) : dataData(y,index).localeCompare(dataData(x,index), undefined, { numeric:true });
  2163. }
  2164. };
  2165. // Sort elements: sort by name if index = 0, else sort by data-value.
  2166. var sortEls = function(els,index,sortDirection) {
  2167. els.detach().sort(function(a,b) {
  2168. return index === 0 ? sortByName(a,b,sortDirection) : sortByData(a,b,index,sortDirection);
  2169. });
  2170. };
  2171. // Sort Dir List
  2172. function sortDirList(index, sortDirection) {
  2173. $dir_list_row.removeClass('sorted');
  2174.  
  2175. if ( index == $default_index ) {
  2176. if ( index == $default_index ) {
  2177. index = 0;
  2178. }
  2179. // sort dirs and files separately
  2180. sortEls( $sort_dirs, index, sortDirection );
  2181. sortEls( $sort_files, index, sortDirection );
  2182. if (sortDirection === 1 ) { // append dirs and files according to sort direction
  2183. $.each($sort_dirs, function() { $dir_list_body.append(this); });
  2184. $.each($sort_files, function() { $dir_list_body.append(this); });
  2185. } else {
  2186. $.each($sort_files, function() { $dir_list_body.append(this); });
  2187. $.each($sort_dirs, function() { $dir_list_body.append(this); });
  2188. }
  2189. } else if ( $settings.dirs_on_top === false || index == 0 && index !== $default_index ) { // sort all dirs and files, append, remove body class
  2190. sortEls( $sort_all, index, sortDirection );
  2191. $.each($sort_all, function() { $dir_list_body.append(this); });
  2192. $body.removeClass('is_dirs_on_top');
  2193. } else if ( $settings.dirs_on_top === true || index !== 0 ) { // sort dirs and files separately
  2194. sortEls( $sort_dirs, index, sortDirection );
  2195. sortEls( $sort_files, index, sortDirection );
  2196. if (sortDirection === 1 ) { // append dirs and files according to sort direction
  2197. $.each($sort_dirs, function() { $dir_list_body.append(this); });
  2198. $.each($sort_files, function() { $dir_list_body.append(this); });
  2199. } else {
  2200. $.each($sort_files, function() { $dir_list_body.append(this); });
  2201. $.each($sort_dirs, function() { $dir_list_body.append(this); });
  2202. }
  2203. }
  2204. if ( index === $('#thead').find('#kind').index() || index === $('#thead').find('#ext').index() ) {
  2205. $body.addClass('is_dirs_on_top');
  2206. addSortStyle( index, sortDirection );
  2207. }
  2208. }
  2209. // Add Sort Styles (for rules)
  2210. function addSortStyle(index, sortDirection) {
  2211. var $this_value, $next_value;
  2212. $dir_list_row.removeClass('sorted');
  2213.  
  2214. $dir_list_row.filter(':visible').not('.dir').add('.app').each(function() {
  2215. $this_value = $(this).find('td').eq(index).attr('data-value');
  2216. $next_value = $(this).next('tr:visible').find('td').eq(index).attr('data-value');
  2217.  
  2218. if ( $this_value !== $next_value ) {
  2219. $(this).addClass('sorted');
  2220. }
  2221. if ( $(this).attr('data-type') !== $(this).next('tr:visible').attr('data-type') ) {
  2222. $(this).addClass('sorted');
  2223. }
  2224. });
  2225. // for last dir
  2226. if ( sortDirection == 1 ) {
  2227. $dir_list_row.filter('.dir').last().addClass('sorted');
  2228. }
  2229. }
  2230. // Sort on click
  2231. function clickSort(x,y) { // x = clicked sort link, y = bool (false ? don't set query_prefs)
  2232. currentIndex = x.index();
  2233. // Only reverse sort order after clicking a sort column once.
  2234. if ( currentIndex != prevIndex ) {
  2235. prevIndex = currentIndex;
  2236. $sort_order = 1;
  2237. }
  2238.  
  2239. sortDirList(currentIndex, $sort_order);
  2240. $sort_order = -1 * $sort_order;
  2241.  
  2242. x.toggleClass('up').addClass('checked').siblings().removeClass('checked up');
  2243. $('#sort_menu').find('li').eq(currentIndex).addClass('checked').siblings().removeClass('checked');
  2244.  
  2245. setContentHeight();
  2246. // set sorting query pref
  2247. if ( y === true ) {
  2248. $query_prefs = getQueryPrefs();
  2249. $query_prefs.set('sort',$('#sort_menu').find('li').eq(currentIndex).text().toLowerCase() );
  2250. updateQuery();
  2251. }
  2252. }
  2253. // Sort on clicking dir_list sort header
  2254. $('#thead').on('click','th:not(.disabled)',function(e) {
  2255. e.preventDefault();
  2256. e.stopPropagation();
  2257. var i = $(this).index();
  2258. clickSort( $(this),true)
  2259. });
  2260. // Sort Menu: click the list header that contains the selected menu text.
  2261. $('#sort_menu').on('click','li:not(.disabled)',function(e) {
  2262. e.preventDefault();
  2263. e.stopPropagation();
  2264. $(this).addClass('checked').siblings().removeClass('checked');
  2265. $('#theader th[id*="'+ thisText($(this)).slice(0,2) +'" i]').click();
  2266. });
  2267. // Initialize sort
  2268. function initialSort() {
  2269. switch ( $UI_pref_sort ) {
  2270. case 'name':
  2271. i = 0;
  2272. break;
  2273. case 'size':
  2274. i = 1;
  2275. break;
  2276. case 'date':
  2277. i = 2;
  2278. break;
  2279. case 'kind':
  2280. i = 3;
  2281. break;
  2282. case 'extension':
  2283. i = 4;
  2284. break;
  2285. default:
  2286. i = 5;
  2287. }
  2288. $('#theader').find('th').eq(i).click();
  2289. }
  2290. initialSort();
  2291.  
  2292. // ***** END SORTING ***** //
  2293. // ***** END BASIC UI FUNCTIONS ***** //
  2294.  
  2295. // ***** CONTENT PANE ***** //
  2296.  
  2297. var $selected = $dir_list.find('.selected');
  2298. var $playing = $dir_list.find('.playing');
  2299. var $media = $dir_list.find('td').filter('.media:not(.unchecked):not(.ignored)');
  2300.  
  2301. // SELECT ROW on click and set classes for $content_pane
  2302. function selectThis(row) {
  2303. let $grid_selected = $content_grid.find('div.font_grid_item a[href="'+ thisLink(row) +'"]').closest('div').add('div.image_grid_item a[href="'+ thisLink(row) +'"]').closest('div').addBack();
  2304. if ( $content_pane.hasClass('has_grid') ) { // Select corresponding grid item
  2305. row.addClass('selected').siblings().removeClass('selected hovered');
  2306. $grid_selected.addClass('selected').siblings().removeClass('selected');
  2307. } else if ( row.hasClass('audio') ) { // if row is audio, remove playing and selected classes
  2308. row.addClass('selected playing').siblings('.media').removeClass('selected playing hovered');
  2309. } else { // remove classes from rows, but leave .audio with playing
  2310. row.addClass('selected').siblings().removeClass('selected hovered');
  2311. }
  2312. }
  2313.  
  2314. //***** SHOW CONTENT *****//
  2315.  
  2316. // Show Font
  2317. var $font_family_arr = [];
  2318. function showFont(row) {
  2319. var $font_family = thisText(row);
  2320. if ( $font_family_arr.indexOf($font_family) == -1 ) { // if font family is not in array, add it
  2321. $font_family_arr.push($font_family);
  2322. $font_preview_styles.append('@font-face { font-family: "'+ $font_family +'"; src: url("'+ thisLink(row) +'"); }'); // only add style if it doesn't exist
  2323. }
  2324. $content_font.css({ 'font-family':'"'+ $font_family +'"' });
  2325. }
  2326. // Show Grid
  2327. function showGrid() {
  2328. $content_pane.removeClass('has_hidden_grid').addClass('has_grid');
  2329. setContentSource($content_grid);
  2330. setContentTitle();
  2331. setContentHeight();
  2332. selectThis($('.selected'));
  2333. }
  2334. // Hide Grid
  2335. function hideGrid() {
  2336. if ( $content_pane.hasClass('has_grid') ) {
  2337. $content_pane.removeClass('has_grid').addClass('has_hidden_grid');
  2338. }
  2339. }
  2340. // Set Content Source
  2341. function setContentSource(el,link) { // add source attribute to content containers, excluding $content_font
  2342. let $el_ID = $(el).attr('id');
  2343. switch ($el_ID) {
  2344. case 'content_audio':
  2345. $(el).find('audio').attr('src',link);
  2346. break;
  2347. case 'content_image':
  2348. $(el).find('img').attr('src',link);
  2349. break;
  2350. case 'content_font':
  2351. break;
  2352. default: // content_pdf, content_video, or content_iframe
  2353. $(el).removeAttr('srcdoc').attr('src',link);
  2354. }
  2355. }
  2356. // SHOW CONTENT
  2357. function showContent(row) { // show any content excluding grids
  2358. if ( row.length > 0 ) { // needed for left/right arrow nav when there are no valid items (i.e. images or fonts)
  2359. let kind = thisRow(row).hasClass('ignore') ? 'ignored' : thisRow(row).attr('data-kind'); // get file kind
  2360. !['audio','font','image','pdf','video','ignored'].includes(kind) || kind === 'dir' ? kind = 'iframe' : null; // load dirs and any other kind of content except audio, font, image, pdf, or video into iframe
  2361. let link = kind === 'pdf' ? thisLink(row) + '?#view=fitB&scrollbar=1&toolbar=1&navpanes=1' : thisLink(row); // add pdf query str to link
  2362. $content_pane.hasClass('has_grid') ? hideGrid() : null;
  2363. closeAllContent(row); // except audio and grids
  2364. if ( kind === 'ignored' ) {
  2365. $content_pane.addClass('has_ignored');
  2366. } else {
  2367. setContentSource( '#content_'+ kind, link ); // set content container source
  2368. kind === 'font' ? showFont(row) : null; // set $content_font css
  2369. kind === 'image' ? $content_grid.find('a[href="' + thisLink(row) + '"]').parent('div').addClass('selected').siblings().removeClass('selected') : null; // set image source
  2370. kind === 'video' ? closeAudio() : null; // if video, close audio
  2371. }
  2372. thisRow(row).attr('data-kind') === 'dir' ? $content_pane.addClass( 'has_dir' ) : $content_pane.addClass( 'has_'+ kind ); // add content pane class
  2373. setContentHeight();
  2374. setContentTitle();
  2375. kind === 'audio' ? autoLoadCoverArt() : null; // autoload cover art on selecting audio
  2376. }
  2377. }
  2378.  
  2379. //***** CLOSE CONTENT (Close button or Cmd/Ctrl + W) *****//
  2380. // Close Audio
  2381. function closeAudio() {
  2382. $('.audio.playing').removeClass('playing');
  2383. $content_pane.removeClass('has_audio').find('#content_audio_title td').empty();
  2384. $audio.trigger('pause').attr('src','');
  2385. setContentHeight();
  2386. }
  2387. // Close audio button click
  2388. $close_audio.on('click',function(e) {
  2389. e.stopPropagation();
  2390. closeAudio();
  2391. });
  2392. // Close Grid
  2393. function closeGrid() {
  2394. $content_pane.removeClass('has_grid');
  2395. $content_grid.empty().removeClass().attr('style','').find('.image_grid_item, img').attr('style','');
  2396. }
  2397. // Close content excluding audio and grids
  2398. function closeAllContent(row) {
  2399. if ( row !== undefined && row.hasClass('audio') ) {
  2400. return;
  2401. } else {
  2402. let $content_classes = $content_pane.attr('class') !== undefined ? $content_pane.attr('class').split(' ') : []; // get array of content pane classes
  2403. for ( let $content_class of $content_classes ) { // for each content_pane class, define the associated content container, remove class and source
  2404. let $container_name = '#content_'+ $content_class.substring(4);
  2405. let $content_el = $( $container_name ); // define content container from content pane class name
  2406. if ( ['has_font','has_image','has_pdf','has_video','has_iframe','has_dir','has_ignored'].includes($content_class) ) { // if not grid or audio, remove class
  2407. $content_pane.removeClass($content_class);
  2408. if ( $content_class === 'has_font' ) { // remove font style
  2409. $content_font.css({'font-family':''})
  2410. $content_font.css({'font-size':'1em'}); // reset font: comment out to retain font scale after loading other content
  2411. } else if ( $content_class === 'has_image' ) { // remove image src
  2412. $content_image.find('img').attr('src','');
  2413. $content_image.attr('data-image-scale-factor',1).find('img').removeClass('zoom_img').attr('style',''); // reset image: comment out to retain image scale after loading other content
  2414. } else if ( $content_class === 'has_video' ) { // remove video src
  2415. $content_video.trigger('pause').attr('src','');
  2416. } else {
  2417. $content_el.attr('src',''); // remove src for anything else
  2418. $content_iframe.attr('src',''); // remove src for anything else
  2419. }
  2420. }
  2421. }
  2422. $content_title.removeClass().find('#title').empty();
  2423. setContentHeight();
  2424. }
  2425. }
  2426. // CLOSE CONTENT
  2427. function closeContent() {
  2428. if ( $content_pane.hasClass('has_grid') ) {
  2429. closeGrid(); // if has_grid, close grid
  2430. } else if ( $content_pane.hasClass('has_hidden_grid') ) {
  2431. showGrid(); // if has_hidden_grid, close content, show grid
  2432. } else if ( ['has_font','has_image','has_pdf','has_video','has_iframe','has_dir','has_ignored'].some( c => $content_pane.attr('class').split(' ').indexOf( c ) >= 0 ) ) {
  2433. closeAllContent(); // close content if content pane has one of these classes, except audio and grids
  2434. } else { // close audio last of all
  2435. closeAudio();
  2436. }
  2437. setContentHeight();
  2438. }
  2439. $('#close_btn').on('click', function(e) {
  2440. e.preventDefault();
  2441. e.stopPropagation();
  2442. closeContent();
  2443. });
  2444.  
  2445. //***** RESET CONTENT (Reset button or Cmd/Ctrl + R) *****//
  2446. function resetContent() {
  2447. if ( $content_pane.attr('class') === '' ) { window.location = window.location.href } // reload page
  2448. if ( $content_pane.hasClass('has_audio') ) { $audio.prop('currentTime', 0).trigger('pause'); }
  2449. if ( $content_pane.hasClass('has_grid') ) { $grid_btn.click(); }
  2450. if ( $content_pane.hasClass('has_font') ) { $content_font.css({'font-size':'1em'});}
  2451. if ( $content_pane.hasClass('has_image') ) { $content_image.attr('data-image-scale-factor',1).find('img').removeClass('zoom_img').attr('style',''); }
  2452. if ( $content_pane.hasClass('has_video') ) { $content_video.prop('currentTime',0).trigger('pause'); }
  2453. if ( $content_pane.hasClass('has_iframe') || $content_pane.hasClass('has_dir') ) { $('.selected').find('a').click(); }
  2454. setContentHeight();
  2455. }
  2456. $('#reload_btn').on('click', function(e) {
  2457. e.preventDefault();
  2458. e.stopPropagation();
  2459. resetContent();
  2460. // $content_pane.addClass('has_iframe');
  2461. // $content_iframe.attr('src','data:text/plain,<body onclick=\"console.log(window.location.href.slice(0,4)) \"></body>');
  2462. });
  2463.  
  2464. //***** SELECT ROWS *****//
  2465. var prevRow = function(className) {
  2466. return ( !$('.selected').length || !$('.selected').prevAll(':visible:not(.unchecked)').filter(className).length ) ? $('#tbody').find('tr').filter(className).last() : $('.selected').prevAll(':visible:not(.unchecked)').filter(className).first();
  2467. };
  2468. var nextRow = function(className) { // if nothing selected, or if no next row with classname, return first row with classname, else return next row with classname
  2469. return ( !$('.selected').length || !$('.selected').nextAll(':visible:not(.unchecked)').filter(className).length ) ? $('#tbody').find('tr').filter(className).first() : $('.selected').nextAll(':visible:not(.unchecked)').filter(className).first();
  2470. };
  2471.  
  2472. //***** SELECT ROWS by typed string *****//
  2473. var str = '', timer, $next_ltr;
  2474. function timeoutID() {
  2475. timer = window.setTimeout( function() {
  2476. str = '';
  2477. }, 1500 );
  2478. }
  2479. function alphaNav(e) { // Select Dir_List row by typed string; Todo: select next row by nearest letter
  2480. if (typeof timer == 'number' ) {
  2481. window.clearTimeout( timer );
  2482. timer = 0; // id
  2483. }
  2484. timeoutID();
  2485. str += e.key
  2486. str = str.toLowerCase();
  2487. if ( $('#dir_list').find('td.name[data-value^="'+ str +'"]').length ) {
  2488. $('#dir_list').find('td.name[data-value^="'+ str +'"]').first().find('a').click();
  2489. } else {
  2490. // $('#dir_list').find('td.name[data-value^="'+ str +'"]').first().find('a').click();
  2491. null; // replace this with next closest match function
  2492. }
  2493. }
  2494.  
  2495. // Click Prev/Next Arrows
  2496. function prevNextBtn(el) {
  2497. if ( el.attr('id') === 'prev_btn' ) {
  2498. clickRow( prevRow('.image:visible,.font:visible') );
  2499. }
  2500. if ( el.attr('id') === 'next_btn' ) {
  2501. clickRow( nextRow('.image:visible,.font:visible') );
  2502. }
  2503. }
  2504. $content_pane.on( 'click','#prev_btn, #next_btn', function(e) {
  2505. e.stopPropagation();
  2506. e.preventDefault();
  2507. prevNextBtn( $(this) );
  2508. });
  2509. // Click grid button
  2510. $sidebar_header.on('click', '#grid_btn, #show_font_grid, #show_image_grid', function(e) {
  2511. makeGrids(e,$(this));
  2512. showGrid();
  2513. });
  2514.  
  2515. // Click prev/next track buttons
  2516. $('.prev_next_track_btn').on('click',function() {
  2517. $(this).attr('id') === 'prev_track' ? playPrevNextTrack('ArrowLeft') : playPrevNextTrack('ArrowRight') ;
  2518. });
  2519.  
  2520. // SELECT ROWS prev/next media track with arrow keys
  2521. function playPrevNextTrack(key) {
  2522. let $key = key.key;
  2523. $shuffle.find('input').prop('checked') === true ? shuffle() : null;
  2524. $playing = $('.playing');
  2525. let kind = $playing.attr('data-kind');
  2526. if ( $body.hasClass('has_media') ) {
  2527. if ( $playing.length === 0 ) { // if no media selected, select first shuffle or first media file
  2528. if ( $shuffle.find('input').prop('checked') === true ) {
  2529. clickRow( $('.audio').eq($count) );
  2530. } else {
  2531. switch ( $key ) {
  2532. case 'ArrowLeft': clickRow( $media.last() ); break;
  2533. case 'ArrowRight': clickRow( $media.first() ); break;
  2534. }
  2535. }
  2536. }
  2537. if ( $playing.length == 1 ) {
  2538. if ( $shuffle.find('input').prop('checked') === true ) {
  2539. selectThis( $('.audio').eq($count) );
  2540. showContent( $('.audio').eq($count) );
  2541. $shuffleList.length !== 0 ? playMedia('play') : null;
  2542. } else {
  2543. switch ( key ) {
  2544. case 'ArrowLeft':
  2545. if ( $playing.prevAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').length === 0 ) {
  2546. clickRow( $dir_list_row.filter('.media[data-kind="'+ kind +'"]:not(.unchecked)').last() );
  2547. } else {
  2548. clickRow( $playing.prevAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').first() );
  2549. }
  2550. break;
  2551. case 'ArrowRight':
  2552. if ( $playing.nextAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').length === 0 && $loop.find('input').is(':checked') === false ) { // loop playback
  2553. selectThis( $dir_list_row.filter('.media[data-kind="'+ kind +'"]:not(.unchecked)').first() );
  2554. autoLoadCoverArt();
  2555. return true;
  2556. } else if ( $playing.nextAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').length === 0 ) { // else click first media file
  2557. clickRow( $dir_list_row.filter('.media[data-kind="'+ kind +'"]:not(.unchecked)').first() );
  2558. } else {
  2559. clickRow( $playing.nextAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').first() ); // else click next media file
  2560. }
  2561. break;
  2562. }
  2563. }
  2564. playMedia('play');
  2565. }
  2566. }
  2567. }
  2568.  
  2569. // SELECT ROWS by arrow keys
  2570. function leftRightArrowNavigation(key,kind) {
  2571. if ( $body.hasClass('has_media') && $('.selected').hasClass('audio') && !$('.selected').hasClass('playing') ) { // play selected track (e.g., after up/down arrow)
  2572. clickRow( $('.audio.selected') );
  2573. playMedia('play');
  2574. } else if ( $body.hasClass('has_media') && $('.selected').hasClass('audio')) { // play next audio track
  2575. playPrevNextTrack(key);
  2576. playMedia('play');
  2577. } else { // select prev/next row of same sort kind
  2578. if ( key === 'ArrowLeft' ) { clickRow( prevRow('[data-kind="'+ kind +'"]:visible') ); }
  2579. if ( key === 'ArrowRight' ) { clickRow( nextRow('[data-kind="'+ kind +'"]:visible') ); }
  2580. if ( $('.selected').hasClass('video') ) { playMedia('play') }
  2581. }
  2582. }
  2583. function arrowNavigation(e) {
  2584. let $key = e.key;
  2585. let $selected = $('.selected');
  2586. let kind = $('.selected').attr('data-kind') === undefined ? $dir_list.find('tbody tr:visible').first().attr('data-kind') : $('.selected').attr('data-kind');
  2587. if ( $content_pane.hasClass('has_grid') ) { // Grid navigation: L/R arrow selects images and fonts only; U/D arrow clicks row.
  2588. switch ( $key ) {
  2589. case 'ArrowUp': clickRow( prevRow('.dir:visible,.file:visible') ); break;
  2590. case 'ArrowDown': clickRow( nextRow('.dir:visible,.file:visible') ); break;
  2591. case 'ArrowLeft':
  2592. if ( $content_grid.hasClass('has_font_grid') ) {
  2593. selectThis( prevRow('.font:visible') );
  2594. } else if ( $content_grid.hasClass('has_image_grid') ) {
  2595. selectThis( prevRow('.image:not(.ignore):visible') );
  2596. } else {
  2597. selectThis( prevRow('.image:not(.ignore):visible,.font:visible') );
  2598. }
  2599. break;
  2600. case 'ArrowRight':
  2601. if ( $content_grid.hasClass('has_font_grid') ) {
  2602. selectThis( nextRow('.font:visible') );
  2603. } else if ( $content_grid.hasClass('has_image_grid') ) {
  2604. selectThis( nextRow('.image:not(.ignore):visible') );
  2605. } else {
  2606. selectThis( nextRow('.image:not(.ignore):visible,.font:visible') );
  2607. }
  2608. break;
  2609. }
  2610. } else {
  2611. switch ( $key ) {
  2612. case 'ArrowUp': // if audio player not visible, show and load audio when selected; else if audio player visible, select but don't load prev/next audio file.
  2613. if ( prevRow('.dir:visible,.file:visible').hasClass('audio') ) {
  2614. closeAllContent(); // except audio and grids
  2615. if ( !$content_pane.hasClass('has_audio') ) {
  2616. clickRow( prevRow('.audio:visible') );
  2617. $('.selected:not(.audio)').removeClass('selected');
  2618. } else {
  2619. prevRow('.audio:visible').addClass('selected').siblings().removeClass('selected hovered');
  2620. autoLoadCoverArt();
  2621. }
  2622. } else {
  2623. clickRow( prevRow('.dir:visible,.file:visible') );
  2624. }
  2625. break;
  2626. case 'ArrowDown':
  2627. if ( nextRow('.dir:visible,.file:visible').hasClass('audio') ) { // if next row is audio...
  2628. closeAllContent(); // except audio and grids
  2629. if ( !$content_pane.hasClass('has_audio') ) { // if audio player is closed, click next row to load and show file
  2630. clickRow( nextRow('.audio:visible') );
  2631. $('.selected:not(.audio)').removeClass('selected');
  2632. } else { // else simply select the next audio file without closing currently playing audio
  2633. nextRow('.audio:visible').addClass('selected').siblings().removeClass('selected hovered');
  2634. autoLoadCoverArt();
  2635. }
  2636. } else {
  2637. clickRow( nextRow('.dir:visible,.file:visible') );
  2638. }
  2639. break;
  2640. case 'ArrowLeft':
  2641. leftRightArrowNavigation('ArrowLeft',kind);
  2642. break;
  2643. case 'ArrowRight':
  2644. leftRightArrowNavigation('ArrowRight',kind);
  2645. break;
  2646. }
  2647. }
  2648. scrollThis('dir_list','selected');
  2649. $content_pane.hasClass('has_grid') && $content_grid.find('.selected').length > 0 ? scrollThis('content_grid','selected') : null;
  2650. }
  2651.  
  2652. //***** CLICK & HOVER EVENTS for Content *****//
  2653.  
  2654. // HOVER Grid Item
  2655. $content_grid.on('mouseenter','> div:not(.selected)',function() {
  2656. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).addClass('hovered');
  2657. }).on('mouseleave','> div:not(.selected)',function() {
  2658. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).removeClass('hovered');
  2659. });
  2660. // HOVER Dir_list_row and highlight corresponding grid item
  2661. $dir_list_row.hover(function() {
  2662. if ( $content_grid.is(':visible') ) {
  2663. $content_grid.find('[href*="'+ thisLink(this) +'"]').closest('div').addClass('hovered');
  2664. }
  2665. }, function() {
  2666. if ( $content_grid.is(':visible') ) {
  2667. $content_grid.find('.hovered').removeClass('hovered');
  2668. }
  2669. });
  2670. // CLICK grid item
  2671. function gridItemClick(e,el) {
  2672. e.preventDefault();
  2673. clickThis($dir_list.find('a[href*="'+ thisLink(el) +'"]') );
  2674. }
  2675. $content_grid.on('click','div', function(e) {
  2676. gridItemClick(e,$(this));
  2677. });
  2678.  
  2679. // CLICK Row
  2680. function clickRow(row) { // Pause/Play audio if .selected = audio, else click row normally
  2681. if ( thisRow(row).hasClass('playing') && !thisRow(row).hasClass('video') ) {
  2682. playPauseMedia();
  2683. } else {
  2684. selectThis( thisRow(row) );
  2685. showContent( thisRow(row) );
  2686. }
  2687. hideMenus();
  2688. }
  2689. // CLICK Row
  2690. $dir_list_row.on('click','a', function(e) {
  2691. e.preventDefault();
  2692. e.stopPropagation();
  2693. clickRow( $(this) );
  2694. });
  2695. // Click element anchor
  2696. function clickThis(el) {
  2697. el.find('a').length > 0 ? el.find('a').click() : el.click();
  2698. }
  2699. // DOUBLE-CLICK Row to open directory
  2700. function doubleClickRow(row) {
  2701. if ( row.hasClass('dir') ) {
  2702. $query_prefs = getQueryPrefs();
  2703. var $history = $query_prefs.get('history') === null ? '' : '+'+ $query_prefs.get('history');
  2704. var $current_index = row.prevAll('.dir:visible:not(.ignore)').length;
  2705. $history = $current_index + $history; // update history
  2706. $query_prefs.delete('selected');
  2707. $query_prefs.set('history',$history);
  2708. updateQuery();
  2709. window.location = row.find('a').attr('href') +'?'+ $query_prefs;
  2710. }
  2711. }
  2712. $dir_list_row.filter('.dir').on('dblclick', function(e) {
  2713. e.preventDefault();
  2714. e.stopPropagation();
  2715. doubleClickRow( $(this) );
  2716. });
  2717.  
  2718. //***** AUTOLOAD CONTENT: index files or files from the file shortcut list *****//
  2719. function autoSelectFile() {
  2720. if ( $UI_pref_selected !== '' ) {
  2721. selectThis( $dir_list.find('tbody tr.dir:not(.invisible)').eq($UI_pref_selected) );
  2722. showContent( $dir_list.find('tbody tr.dir:not(.invisible)').eq($UI_pref_selected) );
  2723. } else if ( $UI_pref_file !== '' ) {
  2724. clickThis( $dir_list_row.find('a:contains('+ $UI_pref_file +')') );
  2725. } else if ( $body.hasClass('autoload_index_files') && $dir_list.find( 'a[href*="/index."]').length > 0 ) { // else load index file
  2726. clickThis( $dir_list.find('a[href*="/index."]') );
  2727. } else if ( $body.hasClass('autoload_media') && $body.hasClass('has_media') ) { // else if audio and images, load cover art
  2728. clickThis( $dir_list.find('.audio,.video').first() );
  2729. } else { // else select first non-media item
  2730. // clickThis( $dir_list.find('tbody tr:not(.media):not(.invisible)').first() );
  2731. }
  2732. scrollThis('dir_list','selected');
  2733. scrollThis('dir_list','playing');
  2734. }
  2735. autoSelectFile();
  2736.  
  2737. // Autoload Cover Art
  2738. function autoLoadCoverArt() { // autoload cover art for audio if dir contains audio and images, and audio is loaded and non-image content is not loaded
  2739. if ( !$body.hasClass('autoload_media') ) {
  2740. return;
  2741. } else if ( $body.hasClass('has_audio') && $body.hasClass('has_images') && $content_pane.hasClass('has_audio') && $content_image.siblings().attr('src') === undefined ) {
  2742. var $cover;
  2743. var $this_ext;
  2744. const $image_titles = ['cover','front'];
  2745. for ( let i = 0; i < $image_titles.length; i++ ) { // select first image whose title begins with $image_titles[i] or which contains $image_titles[i].
  2746. $cover = $dir_list.find('.image').find('td.name[data-value^="'+ $image_titles[i] +'"]:not([data-value*="back"])').parent('tr').first() || $dir_list.find('.image').find('td.name[data-value*="'+ $image_titles[i] +'"]:not([data-value*="back"])').parent('tr').first();
  2747. if ( $cover.length === 1 ) {
  2748. showContent($cover);
  2749. $title.empty().text( $cover.find('a').text() );
  2750. setDimensions($cover);
  2751. break;
  2752. } else {
  2753. showContent( $dir_list.find('.image').first() ); // otherwise show the first image
  2754. $title.empty().append($dir_list.find('.image').first().find('a').text() );
  2755. setDimensions($dir_list.find('.image').first());
  2756. break;
  2757. }
  2758. }
  2759. }
  2760. }
  2761. // File shortcuts menu: autoload directory, then auto-select file
  2762. function showFileShortcut(item) {
  2763. $this_link = thisLink(item);
  2764. window.location = $this_link.slice( 0, $this_link.lastIndexOf('/') );
  2765. }
  2766.  
  2767. //***** KEYBOARD EVENTS *****//
  2768. $body.on('keydown',$dir_list,function(e) {
  2769. setContentHeight();
  2770. $(':focus').blur(); // Need this to able to select grid items after clicking close button (or other buttons).
  2771. let $dir_list_row = $dir_list.find('tbody tr:not(.unchecked)').filter(':visible');
  2772. let $selected = $dir_list.find('tbody tr.selected');
  2773. let $selected_href = thisLink($selected);
  2774. let $playing = $dir_list_row.filter('.playing');
  2775. var $time;
  2776.  
  2777. switch ( e.key ) {
  2778.  
  2779. case 'ArrowUp':
  2780. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) { // Cmd/Ctrl + up arrow = go to parent directory
  2781. if ( $('#parents_dir_menu + ul a').length < 1 ) {
  2782. return;
  2783. } else {
  2784. $('#parent_dir_menu a').trigger('click');
  2785. }
  2786. break;
  2787. }
  2788. if ( $('*[contentEditable="true"]').is(':focus') ) { // Allow arrow navigation within content_editable elements
  2789. return;
  2790. }
  2791. e.preventDefault();
  2792. arrowNavigation(e);
  2793. break;
  2794.  
  2795. case 'ArrowDown':
  2796. if ( (e.ctrl || e.metaKey) && $selected.hasClass('app') && $settings.apps_as_dirs === false ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable or open dir
  2797. return;
  2798. } else if ( $('*[contentEditable="true"]' ).is(':focus') ) {
  2799. return;
  2800. } else if ( (e.ctrl || e.metaKey) && $selected.hasClass('dir') ) {
  2801. $selected.find('a').trigger('dblclick');
  2802. break;
  2803. }
  2804. e.preventDefault();
  2805. arrowNavigation(e);
  2806. break;
  2807.  
  2808. case 'ArrowLeft':
  2809. if ( (e.ctrl || e.metaKey) || (e.ctrl && e.metaKey) ) {
  2810. return;
  2811. } else if ( $('*[contentEditable="true"]').is(':focus') ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable
  2812. return;
  2813. }
  2814. if ( !e.metaKey && (e.altKey && e.shiftKey) || e.altKey ) { // Skip media -30s/-10s
  2815. mediaSkip(e);
  2816. return;
  2817. } else {
  2818. arrowNavigation(e);
  2819. }
  2820. break;
  2821.  
  2822. case 'ArrowRight':
  2823. if ( e.metaKey && !e.altKey && !e.shiftKey && $selected.hasClass('dir') ) { // Open dir with Cmd/Ctrl + Right Arrow
  2824. $selected.find('a').trigger('dblclick');
  2825. return;
  2826. }
  2827. if ( (e.ctrl || e.metaKey) || (e.ctrl && e.metaKey) ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable
  2828. return;
  2829. } else if ( $('*[contentEditable="true"]').is(':focus') || $selected.hasClass('dir ignore') ) {
  2830. return;
  2831. }
  2832. if ( !e.metaKey && (e.altKey && e.shiftKey) || e.altKey ) { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  2833. mediaSkip(e);
  2834. return;
  2835. } else {
  2836. arrowNavigation(e);
  2837. }
  2838. break;
  2839.  
  2840. case ' ': // space
  2841. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2842. alphaNav(e);
  2843. }
  2844. if ( $content_pane.hasClass('has_audio') || $content_pane.hasClass('has_video') ) { // Play/pause media (space bar)
  2845. e.preventDefault();
  2846. playPauseMedia();
  2847. }
  2848. break;
  2849.  
  2850. case 'Enter': // Open directories (or ignore)
  2851. if ( $selected.hasClass('app') && $settings.apps_as_dirs === false ) {
  2852. break;
  2853. } else {
  2854. if ( $selected.hasClass('dir') ) {
  2855. $selected.find('a').trigger('dblclick');
  2856. } else if ( $content_pane.hasClass('has_grid') ) {
  2857. $body.find('#dir_list').find('.selected a').click();
  2858. } else {
  2859. clickThis(thisRow($selected));
  2860. if ( $selected.hasClass('media') && !$content_pane.hasClass('has_grid') ) { // Use return to play/pause media
  2861. let e = new Event('keydown');
  2862. e.key = ' ';
  2863. }
  2864. }
  2865. }
  2866. break;
  2867.  
  2868. // Alphabetical navigation
  2869. 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 'W': case 'X': case 'Y': case 'Z':
  2870. case 'a': case 'b': case 'c': case '': case 'e': case 'f': case '': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case '': case 'p': case 'q': case '': case 's': case 't': case 'u': case 'v': case '': case 'x': case 'y': case 'z':
  2871. 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 '…': case 'π':
  2872. // 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 'ϖ':
  2873. // 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 'Π':
  2874. 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 '≠':
  2875. case '⁄': case '€': case '‹': case '›': case 'fl': case '‡': case '°': case '·': case '‚': case '±': case '¯': case '˘': case '¿': case '`':
  2876. case 'ı': case '': case '´': case '': case '˝': case 'ˆ': case '': case '˜': case '‰': case 'ˇ': case '¨': case '◊': case '„': case '˛': case '¸':
  2877. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2878. alphaNav(e);
  2879. }
  2880. break;
  2881.  
  2882. case 'd': // Cmd/Ctrl + D: Toggle Details
  2883. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) {
  2884. e.preventDefault();
  2885. e.stopPropagation();
  2886. $details_btn.click();
  2887. }
  2888. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2889. alphaNav(e);
  2890. }
  2891. break;
  2892.  
  2893. case 'g': // Cmd/Ctrl + G: Show image Grid
  2894. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) {
  2895. e.preventDefault();
  2896. e.stopPropagation();
  2897. $grid_btn.click();
  2898. }
  2899. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2900. alphaNav(e);
  2901. }
  2902. break;
  2903.  
  2904. case 'i': // Cmd/Ctrl + I: Toggle Invisibles
  2905. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) {
  2906. e.preventDefault();
  2907. e.stopPropagation();
  2908. $inv_checkbox.find('input').click();
  2909. }
  2910. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2911. alphaNav(e);
  2912. }
  2913. break;
  2914.  
  2915. case 'o': // Cmd/Ctrl + Shift + O: Open selected item in new window
  2916. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2917. alphaNav(e);
  2918. }
  2919. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.shiftKey ) {
  2920. window.open($selected_href);
  2921. }
  2922. break;
  2923.  
  2924. case 'r': // Cmd/Ctrl + Shift + R: Refresh
  2925. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) {
  2926. e.preventDefault();
  2927. e.stopPropagation();
  2928. $content_reload_btn.find('button').click();
  2929. }
  2930. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2931. alphaNav(e);
  2932. }
  2933. break;
  2934.  
  2935. case 'w': // Close content pane if Close button visible with Cmd/Crtl + W
  2936. // Doesn't work in some browsers: can't override default keybinding
  2937. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && ( $content_pane.hasClass('[class^="has_"]') || $content_pane.hasClass('has_grid') ) ) {
  2938. e.preventDefault();
  2939. e.stopPropagation();
  2940. $content_close_btn.find('button').click();
  2941. }
  2942. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2943. alphaNav(e);
  2944. }
  2945. break;
  2946.  
  2947. case '.': // Increase previewed content size
  2948. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2949. alphaNav(e);
  2950. }
  2951. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.shiftKey ) {
  2952. $('#increase').click();
  2953. }
  2954. break;
  2955.  
  2956. case ',': // Decrease previewed content size
  2957. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2958. alphaNav(e);
  2959. }
  2960. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.shiftKey ) {
  2961. $('#decrease').click();
  2962. }
  2963. break;
  2964.  
  2965. case 'tab':
  2966. break;
  2967.  
  2968. } // end switch
  2969. });
  2970. // ***** END KEYBOARD EVENTS ***** //
  2971.  
  2972. // ***** GRID SETUP ***** //
  2973. // Create Font Grid Items
  2974. const fontGridItems = function() {
  2975. var $font_grid_items_arr = [];
  2976. $dir_list_row.filter('.font').each(function() {
  2977.  
  2978. let $this_link = $(this).find('a').attr('href');
  2979. let $font_family = thisText($(this));
  2980. let $display_name = $(this).find('a').text().slice( 0,$font_family.lastIndexOf('.') ).replace(/[_|-]/g,' ');
  2981.  
  2982. if ( $font_family_arr.indexOf($font_family) == -1 ) {
  2983. $font_family_arr.push($font_family);
  2984. $font_preview_styles.append('@font-face { font-family: "'+ $font_family +'"; src: url("'+ $this_link +'"); }'); // only add style if it doesn't exist
  2985. }
  2986. $font_grid_item_el.empty().append('<p>'+ $display_name +'</p><h2 style=\'font-family: "'+ $font_family +'"\'; ><a href="'+ $this_link +'">'+ $display_name +'</a></h2>' );
  2987. $font_grid_items_arr.push($font_grid_item_el.clone());
  2988. });
  2989. return $font_grid_items_arr;
  2990. };
  2991. // Create Image Grid Items
  2992. const imageGridItems = function() {
  2993. var $image_grid_items_arr = [];
  2994. $dir_list_row.filter('.image').each(function() {
  2995.  
  2996. let $this_link = thisLink(this);
  2997. let $this_ext = thisExt(this);
  2998. let exts = $row_types.image.filter( ext => $.inArray(ext, $row_settings.ignore) == -1 );
  2999. let $display_name = $this_link.slice($this_link.lastIndexOf('/') + 1).replace(/[_|-]/g,' ');
  3000.  
  3001. if ( $.inArray( $this_ext, exts ) > -1 ) { // if this row file ext is in the image extension array
  3002. $image_grid_item_el.empty().append('<a href="'+$this_link+'"><img src="'+$this_link+'" title="'+$display_name+'" /></a>');
  3003. $image_grid_items_arr.push( $image_grid_item_el.clone() );
  3004. }
  3005. });
  3006. return $image_grid_items_arr;
  3007. };
  3008. // Make Grids
  3009. function makeGrids(e,el) {
  3010. e.stopPropagation();
  3011. closeGrid();
  3012. $content_pane.removeClass('has_hidden_grid').addClass('has_grid');
  3013. if ( el.attr('id') === 'grid_btn' ) {
  3014. $title.removeClass();
  3015. fontGridItems().length === 0 ? $content_grid.addClass('has_image_grid') : $content_grid.addClass('has_grid');
  3016. $content_grid.append( imageGridItems(), fontGridItems() );
  3017. } else if ( el.attr('id') === 'show_font_grid' ) {
  3018. $title.removeClass().addClass('font_grid');
  3019. $content_grid.addClass('has_font_grid');
  3020. $content_grid.append( fontGridItems() );
  3021. } else {
  3022. $title.removeClass().addClass('image_grid');
  3023. $content_grid.addClass('has_image_grid');
  3024. $content_grid.append( imageGridItems() );
  3025. }
  3026. }
  3027.  
  3028. // ***** SCALE PREVIEWED IMAGES & FONTS ***** //
  3029. var $em = parseInt(getComputedStyle(document.body).fontSize); // pts/em
  3030. var $font_incr = 1.125; // scale factor for font size increase/decrease
  3031. var $image_incr = 1.125;
  3032. var $image_width;
  3033. var $image_height;
  3034. var $image_scale_factor;
  3035. var $image_grid_item_size;
  3036. var $grid_scale_factor;
  3037.  
  3038. var fontSize = function(el) {
  3039. return parseFloat(el.css('font-size'));
  3040. };
  3041. // Scale Fonts
  3042. function scaleFonts(incr, y) {
  3043. if ( y === -1 ) { incr = 1/incr; }
  3044. if ( $content_pane.hasClass('has_grid') && ( $content_grid.hasClass('has_font_grid') || $content_grid.hasClass('has_grid') ) ) {
  3045. $content_grid.css({'font-size':( fontSize($content_grid)/$em * incr ) +'em'});
  3046. return;
  3047. }
  3048. if ( $content_pane.hasClass('has_font') ) {
  3049. $content_font.css({'font-size':( fontSize($content_font)/$em * incr ) +'em'});
  3050. return;
  3051. }
  3052. }
  3053. // Scale Images
  3054. function scaleImages(incr, y) {
  3055. if (y === -1 ) { incr = 1/incr; }
  3056. if ( $content_pane.hasClass('has_image') && !$content_pane.hasClass('has_grid') ) {
  3057. $image_width = parseFloat( $content_image.find('img').width() ) * incr; // increment image size
  3058. $image_height = parseFloat( $content_image.find('img').height() ) * incr; // increment image size
  3059. $image_scale_factor = parseFloat( $content_image.attr('data-image-scale-factor') ); // get scale factor
  3060.  
  3061. $content_image.attr('data-image-scale-factor', parseFloat( $image_scale_factor ) * incr ).find('img').removeClass('zoom_img').css({'width':$image_width +'px', 'max-width':'none', 'max-height':'none','top':'0','transform':'translateY(0)'});
  3062.  
  3063. $content_image.scrollLeft( ( $image_width - $(window).width() )/2 ) ;
  3064. if ( ( ( $image_height - $(window).height() )/2 ) > 0 ) {
  3065. $content_image.scrollTop( ( $image_height - $(window).height() )/2 );
  3066. }
  3067. return;
  3068. }
  3069. if ( $content_pane.hasClass('has_grid') && ( $content_grid.hasClass('has_image_grid') || $content_grid.hasClass('has_grid') ) ) {
  3070. $image_grid_item_size = parseFloat( $('.image_grid_item').width() * incr);
  3071. $grid_scale_factor = parseFloat( $content_grid.attr('data-grid-scale-factor') );
  3072.  
  3073. $content_grid.attr('data-grid-scale-factor', parseFloat( $grid_scale_factor ) * incr ).css({'grid-template-columns':'repeat(auto-fit, minmax('+ ($image_grid_item_size) +'px, 1fr))', 'grid-template-rows':'repeat(auto, minmax('+ ($image_grid_item_size) +'px))' });
  3074. $content_grid.find('.image_grid_item').css({'width':$image_grid_item_size +'px', 'height':$image_grid_item_size +'px'}); // grid items are square, so width = height
  3075. $content_grid.find('img').css({'max-width':( $image_grid_item_size - $image_grid_item_size/8 ) +'px', 'max-height':( $image_grid_item_size - $image_grid_item_size/8) +'px'});
  3076. return;
  3077. }
  3078. }
  3079. // Scale Fonts and Images
  3080. function scalePreviewItems(y) { // combine scaling into one function
  3081. scaleImages( $image_incr, y );
  3082. scaleFonts( $font_incr, y );
  3083. }
  3084. // Scale Content
  3085. $content_scale.on('click','span',function() {
  3086. if ( $(this).attr('id') === 'increase' ) {
  3087. scalePreviewItems(1);
  3088. }
  3089. if ( $(this).attr('id') === 'decrease' ) {
  3090. scalePreviewItems(-1);
  3091. }
  3092. });
  3093. // Zoom Images on click
  3094. function zoomImage(x,e) {
  3095. $this_link = $(x).attr('src');
  3096. var $offset = $(x).offset();
  3097. var $this_width = $(x).width();
  3098. var $this_height = $(x).height();
  3099.  
  3100. $(x).toggleClass('zoom_img');
  3101. ( $(x).attr('style') !== '' || $(x).attr('style') !== undefined ) ? $(x).attr('style','') : null;
  3102. $content_image.find('img:not(.zoom_img)').css({'max-height':$content_image.innerHeight() - 52 + 'px'})
  3103. if ( $this_link !== undefined ) {
  3104. getDimensions( $this_link, function( width, height ) {
  3105. $content_image.scrollLeft( (e.pageX - $offset.left) * width/($this_width + 13) - ( e.pageX - $offset.left ) - 52 ) ;
  3106. $content_image.scrollTop( (e.pageY - $offset.top) * height/($this_height + 13) - ( e.pageY - $offset.top ) - 52 );
  3107. });
  3108. }
  3109. }
  3110. $content_image.on('click', 'img', function(e) {
  3111. zoomImage( this,e )
  3112. });
  3113. // ***** END SCALE PREVIEW ITEMS ***** //
  3114.  
  3115. // ***** AUDIO CONTENT ***** //
  3116.  
  3117. var $count = 0;
  3118. var $playlist;
  3119. var $shuffleList = [];
  3120. var $next;
  3121. var $task;
  3122. // Update Playlist
  3123. function updatePlaylist() {
  3124. $playlist = [];
  3125. $dir_list_row.filter('.audio:not(.unchecked)').each(function() {
  3126. $this_link = thisLink(this);
  3127. $playlist.push($this_link);
  3128. });
  3129. return $playlist;
  3130. }
  3131. // Initialize Shuffle List
  3132. function initShuffleList() {
  3133. updatePlaylist();
  3134. $shuffleList = [];
  3135. for ( let i = 0; i < $playlist.length; i += 1 ) {
  3136. $shuffleList.push(i);
  3137. }
  3138. return $shuffleList;
  3139. }
  3140. // Update Play Count
  3141. function updateCount() {
  3142. $count = $playing.index('.audio:not(.unchecked)');
  3143. }
  3144. // Check/Uncheck Audio/Video Files
  3145. function toggleChecked(e) {
  3146. e.stopPropagation();
  3147. $(this).blur();
  3148. thisRow(this).toggleClass('unchecked');
  3149. updateCount();
  3150. updatePlaylist();
  3151. initShuffleList();
  3152. }
  3153. $dir_list_row.filter('.media').on('click','input', toggleChecked );
  3154. // Check/Uncheck all Audio/Video Files
  3155. function toggleAllChecked(e) {
  3156. e.stopPropagation();
  3157. $dir_list_row.find('input').trigger('click');
  3158. updatePlaylist();
  3159. initShuffleList();
  3160. }
  3161. $dir_list.find('#play_toggle').on('click', toggleAllChecked );
  3162.  
  3163. // Is Playing
  3164. function isPlaying(el) {
  3165. return (el.get(0).currentTime > 0 && !el.get(0).paused && !el.get(0).ended);
  3166. }
  3167. // Play Media
  3168. function playMedia(task) {
  3169. $('.playing').hasClass('audio') ? $audio.trigger(task) : $content_video.trigger(task);
  3170. }
  3171. // Skip media tracks +/-10/30 seconds
  3172. function mediaSkip(e) {
  3173. let factor = e.key === 'ArrowLeft' ? -1 : 1;
  3174. if ( e.altKey && e.shiftKey ) {
  3175. n = 30;
  3176. } else if ( e.altKey ) {
  3177. n = 10;
  3178. }
  3179. let $media = $('.playing').hasClass('audio') ? $audio : $content_video;
  3180. let $time = $media.prop('currentTime');
  3181. $media.prop('currentTime', $time + factor*(n));
  3182. }
  3183.  
  3184. // Play/Pause Audio/Video
  3185. function playPauseMedia() {
  3186. if ( $content_pane.hasClass('has_audio') ) {
  3187. isPlaying($audio) ? $audio.trigger('pause') : $audio.trigger('play');
  3188. }
  3189. if ( $content_pane.hasClass('has_video') ) {
  3190. isPlaying($content_video) ? $content_video.trigger('pause') : $content_video.trigger('play');
  3191. }
  3192. }
  3193. // Play Next Track
  3194. function playPrevNextTrackBtn(el) {
  3195. e = $.Event("keydown");
  3196. e.key = el.attr('id') === "prev_track" ? 'ArrowLeft' : 'ArrowRight';
  3197. playPrevNextTrack(e);
  3198. }
  3199. // Prev/Next Track buttons
  3200. $('.prev_next_track_btn').on( 'click', function() { playPrevNextTrackBtn( $(this) ); });
  3201.  
  3202. // Toggle Shuffle Play
  3203. function toggleShuffle() {
  3204. let $rand = Math.floor( Math.random() * ($media.length) );
  3205. if ( $shuffle.find('input').prop('checked') ) {
  3206. ( !isPlaying($audio) && !isPlaying($content_video) ) ? clickThis( $media.eq($rand) ) : null; // if no media playing, select random track; if media playing, do nothing, then shuffle.
  3207. updatePlaylist();
  3208. initShuffleList();
  3209. $count = Math.floor( Math.random() * $shuffleList.length );
  3210. }
  3211. }
  3212. $shuffle.on('click', toggleShuffle );
  3213.  
  3214. // Shuffle Play
  3215. function shuffle() {
  3216. if ( $shuffleList.length <= 1 && $loop.find('input').prop('checked') ) { // If loop is checked, and all songs have been shuffled...
  3217. initShuffleList();
  3218. $next = Math.floor( Math.random() * $shuffleList.length ); // choose a random index number from the remaining unplayed songs,
  3219. $count = $shuffleList[ $next ]; // set the playing $count to the index
  3220. } else if ( $shuffleList.length === 0 ) { // else if all songs have been shuffled, select the first song and stop;
  3221. $count = 0;
  3222. } else {
  3223. $next = Math.floor( Math.random() * $shuffleList.length ); // else choose a random index number from the remaining unplayed songs,
  3224. $count = $shuffleList[ $next ]; // set the playing $count to the index
  3225. $shuffleList.splice( $next, 1 ); // remove the indexed value from the unplayed list,
  3226. }
  3227. }
  3228. // Initialize Audio
  3229. function initMedia() {
  3230. $('#audio, #content_video').on('ended', function() {
  3231. playPrevNextTrack('ArrowRight');
  3232. scrollThis('dir_list','playing');
  3233. });
  3234. }
  3235. initMedia();
  3236. // ***** END AUDIO CONTENT ***** //
  3237.  
  3238. // ***** OTHER FUNCTIONS ***** //
  3239.  
  3240. // Resize Sidebar/Content Pane
  3241. function resizeSidebar(f) {
  3242. f.stopPropagation();
  3243. var $startX = f.pageX;
  3244. var $sidebar_width = $sidebar_wrapper.width();
  3245. var $window_width = window.innerWidth;
  3246.  
  3247. $(document).on('mousemove',function(e) {
  3248. e.stopPropagation();
  3249. e.preventDefault();
  3250. var $deltaX = e.pageX - $startX;
  3251. if ( e.pageX > 200 && e.pageX < $window_width - 200 ) {
  3252. $sidebar_wrapper.css({'width':$sidebar_width + $deltaX + 'px'});
  3253. $content_pane.css({'width':($window_width - $sidebar_width) - $deltaX + 'px'});
  3254. }
  3255. setContentHeight();
  3256. });
  3257. $(document).on('mouseup',function() {
  3258. $(document).off('mousemove');
  3259. $sidebar_width = $sidebar_wrapper.width();
  3260. $query_prefs = getQueryPrefs();
  3261. $query_prefs.set('width',$sidebar_width);
  3262. updateQuery();
  3263. });
  3264. }
  3265. $handle.on('mousedown', resizeSidebar );
  3266.  
  3267. // EXPORT SETTINGS
  3268. let $settings_string = unescape( $settings.toSource() );
  3269. $settings_string = $settings_string.replace('JSON.parse\(unescape\("\{','').replace('\}"))','').replace(/\/",/g,'",').replace(/\/"],/g,'"],').replace(/"(\w+?)":/g,'\n\n$1:\t');
  3270.  
  3271. var save = function(filename, data) {
  3272. var blob = new Blob([data], {type: 'text/html'});
  3273. var elem = window.document.createElement('a');
  3274. elem.href = window.URL.createObjectURL(blob);
  3275. elem.download = filename;
  3276. document.body.appendChild(elem);
  3277. elem.click();
  3278. document.body.removeChild(elem);
  3279. URL.revokeObjectURL(blob);
  3280. }
  3281.  
  3282. $export_settings.on('click','a',function(e) {
  3283. e.preventDefault();
  3284. save("settings.txt",$settings_string);
  3285. });
  3286.  
  3287. })();