Supercharged Local Directory File Browser

Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds navigation links, file preview pane, keyboard navigation, user-defined shortcuts, filtering, more.

2018-12-13 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name Supercharged Local Directory File Browser
  3. // @version 3.1.2
  4. // @description Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds navigation links, file preview pane, keyboard navigation, user-defined shortcuts, filtering, 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. // @include *michaelschrauzer.com/backups*
  14. // @include *downloads.it.ox.ac.uk/*/
  15. // @include *otrrlibrary.org/*
  16. // @include *hypem.com*
  17. // @include http://ftp.gnu.org/*/
  18. // @include https://hypem.com/*/
  19. // @include https://nodejs.org/dist*
  20. // @include http://www.caratulas.com*/
  21. // @include *fromv.ir/*/
  22. // @include https://cdimage.parrotsec.org/parrot/iso*
  23. // @include http://mirror.centos.org/centos*
  24.  
  25. // @require http://code.jquery.com/jquery-latest.min.js
  26.  
  27. // NOTE: This script was developed in Vivaldi, running on Mac OS High Sierra. It has been tested in various Chrome and Gecko-based browsers.
  28. // It has been minimally tested on Windows and not at all on other OSes. It should work, but please report any issues.
  29. // The script does not work on local directories in Safari because Safari does not allow local directories to be browsed, but it will work on remote directories (or on local directories through a local server).
  30.  
  31. // NOTE: By default, Greasemonkey and Tampermonkey will not run scripts on file:/// urls, so for this script to work, you will have to enable it first.
  32. // For Tampermonkey, go to Chrome extension page, and tick the 'Allow access to file URLs' checkbox at the Tampermonkey extension section.
  33. // For Greasemonkey, open about:config and change greasemonkey.fileIsGreaseable to true.
  34.  
  35. // @namespace https://greatest.deepsurf.us/users/16170
  36. // ==/UserScript==
  37.  
  38. (function() {
  39. 'use strict';
  40. var $ = jQuery;
  41.  
  42. // ***** USER SETTINGS ***** //
  43.  
  44. const $settings = {
  45.  
  46. // Paste your exported settings between the two lines below:
  47. //--------------------------------------------------------//
  48.  
  49. shortcuts: // N.B.: Directory links must end with "/", file links must end with another character.
  50. // You may add as many menus and links as you like; just copy the example below and edit as needed.
  51. // Local directory shortcuts must begin with "file:///"; external shortcuts must begin with the correct protocol ("http://" or "ftp://", etc.).
  52. // Note that because of same-origin security concerns, the browser will not allow you to navigate from an external webpage to a local directory.
  53. // But see this page for possible workarounds: https://stackoverflow.com/questions/39007243/cannot-open-local-file-chrome-not-allowed-to-load-local-resource
  54. [
  55. // {
  56. // "menu_title":"My Sample Menu",
  57. // "links":
  58. // [
  59. // { "link_name":"My Directory Link 1", "link":"file:///Path/To/My/Directory/" },
  60. // { "link_name":"My Directory Link 2", "link":"file:///Path/To/My/Directory_2/" },
  61. // { "link_name":"My External Link", "link":"https://www.mywebpage.com/" },
  62. // { "link_name":"My File Link", "link":"file:///Path/To/My/File.ext" },
  63. // ]
  64. // },
  65. {
  66. "menu_title":"Volumes",
  67. "links":
  68. [
  69. { "link_name":"Mac", "link":"file:///Volumes/Mac/" },
  70. { "link_name":"PQI 64GB", "link":"file:///Volumes/PQI 64GB%20USB%20Drive%201/" },
  71. { "link_name":"JETDRIVE 128GB", "link":"file:///Volumes/JETDRIVE%20128GB%20Drive/" },
  72. ]
  73. },
  74. {
  75. "menu_title":"Root Links",
  76. "links":
  77. [
  78. { "link_name":"Applications", "link":"file:///Applications/" },
  79. { "link_name":"Library", "link":"file:///Library/" },
  80. { "link_name":"Users", "link":"file:///Users/" },
  81. { "link_name":"Volumes", "link":"file:///Volumes/" },
  82. ]
  83. },
  84. {
  85. "menu_title":"MacBookPro",
  86. "links":
  87. [
  88. { "link_name":"Desktop", "link":"file:///Users/MacBookPro/Desktop/" },
  89. { "link_name":"Documents", "link":"file:///Users/MacBookPro/Documents/" },
  90. { "link_name":"Downloads", "link":"file:///Users/MacBookPro/Downloads/" },
  91. { "link_name":"Downloads", "link":"file:///Users/MacBookPro/Dropbox/" },
  92. { "link_name":"Library", "link":"file:///Users/MacBookPro/Library/" },
  93. { "link_name":"Own Cloud", "link":"file:///Users/MacBookPro/ownCloud/" },
  94. ]
  95. },
  96. {
  97. "menu_title":"Business",
  98. "links":
  99. [
  100. { "link_name":"Fonts System", "link":"file:///Users/MacBookPro/Library/Fonts/" },
  101. { "link_name":"Fonts PQI", "link":"file:///Volumes/PQI%2064GB%20USB%20Drive%201/04%20GRAPHICS%20Fonts/" },
  102. { "link_name":"AP Book Covers", "link":"file:///Users/MacBookPro/Documents/04%20BUSINESS/ANGELICO%20PRESS/AP%20BOOK%20COVERS%20Working/" },
  103. { "link_name":"Invoices", "link":"file:///Users/MacBookPro/Dropbox/DOCUMENTS/INVOICES/" },
  104. { "link_name":"WWW", "link":"file:///Applications/AMPPS/www/ "}
  105. ]
  106. },
  107. {
  108. "menu_title":"Empty Vessel",
  109. "links":
  110. [
  111. { "link_name":"Library", "link":"file:///Users/MacBookPro/Documents/02%20LIBRARY/" },
  112. { "link_name":"The Empty Vessel", "link":"file:///Users/MacBookPro/Documents/02%20LIBRARY/10%20THE%20EMPTY%20VESSEL/" },
  113. { "link_name":"Literature", "link": "file:///Users/MacBookPro/Documents/02%20LIBRARY/P%20LITERATURE/" },
  114. { "link_name":"Defoe", "link": "file:///Users/MacBookPro/Documents/02%20LIBRARY/P%20LITERATURE/00%201660-1731%20DEFOE%20DANIEL/" },
  115. { "link_name":"Working Text Files", "link": "file:///Users/MacBookPro/Documents/02%20LIBRARY/P%20LITERATURE/WORKING%20TEXT%20FILES/" },
  116. ]
  117. },
  118. {
  119. "menu_title":"External Links",
  120. "links":
  121. [
  122. { "link_name":"michaelschrauzer.com/backups", "link":"https://michaelschrauzer.com/backups/" },
  123. { "link_name":"michaelschrauzer.com/backups/buffalo", "link":"https://michaelschrauzer.com/backups/BUFFALO/?dark_theme=true&hide_details=true&sort=name" },
  124. { "link_name":"michaelschrauzer.com/backups/spoken_word", "link":"https://michaelschrauzer.com/backups/BUFFALO/SPOKEN%20WORD/?dark_theme=true&hide_details=true&selected=9&sort=name" },
  125. { "link_name":"Mere Nature","link":"file:///Users/MacBookPro/Documents/02%20LIBRARY/P%20LITERATURE/00%201660-1731%20DEFOE%20DANIEL/DEFOE%201726%20Mere%20Nature%20Delineated.htm" },
  126. ]
  127. }
  128. ],
  129.  
  130. UI_font: // Choose an installed font for the UI.
  131. 'lucidagrande, sans-serif',
  132. UI_font_size: // Choose a default UI font size; use any standard CSS units.
  133. '13px',
  134. alternate_background: // If true (default false), alternate sidebar row background color.
  135. true,
  136. 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;
  137. // 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).
  138. true,
  139. hide_ignored_files: // If true, ignored files will be completely hidden from the file list;
  140. // If false (default), ignored files will appear greyed-out.
  141. false,
  142. hide_invisibles: // Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
  143. true,
  144. hide_details: // If true (default), hide file and directory details; if false, show them.
  145. true,
  146. default_sort: // Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
  147. // default = Chrome sorting: dirs on top, files alphabetical.
  148. 'ext',
  149. dirs_on_top: // If true, directories will always be listed firs except when sorting by "name" (since otherwise sorting by "name" would equal "default").
  150. // 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.)
  151. true,
  152. 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.
  153. // If false (default), treat apps as ignored files.
  154. false,
  155. dark_theme: // If true (default: false), gives the content pane a dark background, and inverts html and text content.
  156. true,
  157. grid_image_size: // Default = 200
  158. 200,
  159. grid_font_size: // Default = 1
  160. 1,
  161. 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).
  162. true,
  163. autoload_index_files: // If true (default: false), automatically select first "index.ext" file found in directory.
  164. false,
  165. use_custom_icons: // if true (default), use custom icons for dirs and files
  166. // if false, use browser/server default icons
  167. true
  168. //--------------------------------------------------------//
  169. // Paste your exported settings between the above two lines.
  170. }
  171.  
  172. // $ROW_TYPES:
  173. // DO NOT DELETE ANY EXISTING CATEGORIES!
  174. // Add file extensions for sorting and custom icon display to the existing categories.
  175. // You can also define your own new categories, but do not add an extension to more than one row_type category.
  176. // Do not add leading "." to the extensions.
  177. const $row_types = {
  178. // myCategory: ['ext1','ext2',],
  179. dir: ['/'],
  180. app: ['app/','app','exe','msi'],
  181. archive: ['zip','rar','cbr','7z','tar','gz','dmg','pkg','archive'],
  182. audio: ['mp3','m4a','aac','aif','aiff','ape','flac','ogg','wav'],
  183. font: ['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'],
  184. graphics: ['indd','idml','indt','icml','ai','eps','pages','qxp','qxb','qxd','mif','sla','dtp','pmd','pub','fm','book','inx'],
  185. htm: ['htm','html','xhtm','xhtml'],
  186. image: ['jpg','jpeg','png','apng','gif','bmp','webp','svg','tif','tiff','psd','raw','dng','cr2','nef','arw'],
  187. office: ['doc','docx','epub','xls','xlsx','xlm','odt','odf','rtf'],
  188. pdf: ['pdf'],
  189. text: ['txt','log','md','nfo'],
  190. code: ['c','conf','css','h','ini','js','json','jsx','less','list','lock','php','plist','py','sass','strings','xml'],
  191. video: ['mpeg','mov','m4v','webm','mp4'],
  192. };
  193.  
  194. // $ROW_SETTINGS: Ignore or Exclude files by extension
  195. const $row_settings = {
  196. ignore: // Files with these extensions will not be loaded if selected in the sidebar (prevents the browser from attempting to download the file).
  197. ['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'],
  198. exclude: // Files with these exensions will not be inverted in dark mode
  199. ['htm','html','xhtm','xhtml'],
  200. };
  201.  
  202. // ***** END USER SETTINGS ***** //
  203.  
  204. // KEYBINDINGS:
  205.  
  206. // Arrow Up/Down: Select prev/next item. If audio is playing, and prev/next file is also audio, it will be selected but not loaded in the audio player; press return to load it.
  207. // Arrow Left/Right: Select prev/next row of the same kind as the current selection, except if current selection is a media file, select and begin playback of that item.
  208. // Cmd/Ctrl + Arrow Up: Go to parent directory
  209. // Opt/Alt + Arrow Left/Right: Skip audio ±10s
  210. // Shift + Opt/Alt + Arrow Left/Right: Skip audio ±30s
  211. // Space: Pause/Play media files
  212. // Return: Open selected directory, select file, or pause/play media.
  213. // Cmd/Ctrl + D: Toggle file details (size, date modified) in some index page types.
  214. // Cmd/Ctrl + G: Show or Reset Grid
  215. // Cmd/Ctrl + I: Toggle Invisibles
  216. // Cmd/Ctrl + Shift + O: Open selected item in new window/tab
  217. // Cmd/Ctrl + R: Reload grids and previewed content, reset scaled images/fonts, reset media files to beginning.
  218. // Cmd/Ctrl + W: Close previewed content (doesn't work in all browsers; use close button instead), or close window if no content is being previewed.
  219. // Cmd/Ctrl + Shift + </>: Scale preview items and grids.
  220.  
  221. // CHANGELOG:
  222.  
  223. //• 3.1.2
  224. //• FIXED: Light theme image grid items had transparent backgrounds.
  225.  
  226. //• 3.1.1
  227. //• CHANGED: If autoload_media = false, don't select anything.
  228. //• FIXED: Image grid columns weren't flexing to fill width.
  229.  
  230. //• 3.1.0
  231. //• Improved font and image grid previews: added unstyled file names for clarity, file names are no longer lowercased, and dashes and underscores are replaced with spaces.
  232. //• Various small UI tweaks.
  233.  
  234. //• 3.0.0
  235. //• NEW: Support for video playback (finally!). Default supported video extensions: .mpeg, .mov, .m4v, .webm, .mp4 (add additional extensions below in $row_types > video).
  236. //• IMPROVED: Cover art auto-selection: files with "back" in the name will be ignored and files with "front" or "cover" in the name will be selected.
  237. //• IMPROVED: Directory preview styling.
  238. //• IMPROVED: Better support for Firefox.
  239. //• ADDED: "Autoload media" menu item.
  240. //• CHANGED: "autoload_audio" renamed to "autoload_media"; if you export your user settings, please this update this item accordingly when you paste them back in.
  241. //• CHANGED: Completely reworked arrow navigation and autoload_media behavior because previous behavior was confusing.
  242. //• Up/Down Arrows will now always select the prev/next row.
  243. //• If the audio player is not visible, it will be displayed and the selected audio file will be loaded for playback.
  244. //• If audio is playing and the prev/next selected file is also audio, it will be selected but not loaded, so that the currently playing audio will not be interrupted. (Press return/enter to load and begin playback of selected audio.)
  245. //• Selecting audio files deselects video files and closes video player.
  246. //• Selecting video files deselects audio files and closes audio player.
  247. //• Left/Right Arrows will now always select the prev/next row of the same sort kind as the currently selected file, except:
  248. //• if the current selection is an audio file, the L/R arrows will select and begin playback of that selection.)
  249. //• Note that if the current selection is _not_ an audio file, any currently playing audio will continue without interruption.
  250. //• FIXED: Media files did not scroll into view when next file began playing.
  251. //• CHANGED: Autoload cover art now works irrespective of "autoload_media" setting.
  252. //• IMPROVED: Left/Right arrow navigation will select prev/next dir or row if there are no media, image, or font files are in the directory.
  253. //• IMPROVED: You can now close previewed content and audio files separately from each other.
  254. //• CHANGED: Audio file titles shown separately from other file or dir titles.
  255. //• IMPROVED: Added string normalization for navigation by typed string: accented characters will be matched by their base ASCII letter (e.g., á = a, Ñ = n, ß = s, etc.).
  256. //• IMPROVED: Added "other" sort category for files whose extensions are not listed in $row_types.
  257. //• FIXED: Parent links were incorrectly formatted.
  258. //• FIXED: Arrow buttons in preview pane were not working properly. Now, if an image or font is displayed, clicking the arrows will select the prev/next image or font file.
  259. //• ADDED: Contact link in shortcuts menu
  260. //• FIXED: "Donate" link.
  261. //• Fixes for various style glitches introduced in the last version.
  262. //• Many, many small fixes.
  263. //• CHANGED: moved $row_types to just below user settings, for easier management and updates.
  264. //• Internal: Reorganized, abstracted, and DRYed many functions; added more comments.
  265.  
  266. //• 2.7.1
  267. //• Added: User settings to choose default UI font-family and UI font size.
  268. //• Added: User setting and menu item to alternate sidebar row background color.
  269. //• Improved: Directory preview styling: alternating row background, font-family and font size honor UI settings.
  270. //• Internal: Completely reorganized CSS by declarations instead of selectors (DRY CSS).
  271. //• FIX: Font previous sometimes forgets dark mode?
  272.  
  273. //• 2.7.0: New features and user settings. Please export your user_settings before updating and note the changes mentioned below.
  274. //• NEW (and experimental): Display a standard index of the selected sidebar directory in the preview pane.
  275. // This is very handy for browsing directories from the sidebar.
  276. // Clicking files and directories directly in the preview pane works as you'd expect, but it can be a little weird,
  277. // since you can navigate up and down the directory structure in the preview pane independently from the sidebar.
  278. //• NEW: Added URL query strings to remember directory selection history and various temporary UI settings between directory changes:
  279. // sort order, show/hide details, default/dark mode, sidebar width, and previously selected directory.
  280. //• NEW: Select files and directories by typed string.
  281. //• Improved: Converted user shortcut settings to JSON for greater flexibility.
  282. // Now you can create and name your own shortcut menu categories.
  283. // IMPORTANT: You must re-enter your old shortcuts in the new settings format.
  284. //• Added: Menu item to reset/delete query strings and return to the default user_settings in the script.
  285. //• Added: New user setting to show/hide details by default. (Don't know why it took so long to add this.)
  286. //• Added: User customizable file kinds for sorting.
  287. // You may add or remove file kind categories and file extensions to suit your own files:
  288. // See "$row_types" in code below, at the beginning of "General Setup" and just after the Changelog, to add your own.
  289. //• Removed "ignore_file_types" from the user_settings, because the list of ignored file extensions has been moved:
  290. // See "$row_settings" in code below, at the beginning of "General Setup" and just after the Changelog, to add your own.
  291. //• Removed "user_name" from the user_settings, because it is no longer needed.
  292. //• Improved: File shortcuts work much more reliably now, and thanks to query strings, you can now bookmark more than one file in a directory.
  293. //• Improved: Various small UI changes.
  294. //• Fixed: Toggle sidebar wasn't working.
  295. //• Fixed: Ignored files weren't being sorted.
  296. //• Internals: Abstracted a bunch of functions, removed some unneeded variables and functions. More to be done.
  297. //• If you like this script, please consider making a donation. See the "Donate" menu item. Thanks!
  298.  
  299. //• 2.6.5
  300. //• Fixed: First and last audio files would repeat when ended instead of going to next track in some circumstances.
  301. //• Fixed: Various issues with file navigation.
  302.  
  303. //• 2.6.4
  304. //• Added: Nice custom icons for dirs and various file types. Other minor UI tweaks.
  305. //• Added: New user setting to use default file icons instead the new custom ones, in case you don't like them.
  306. //• Added: Separate close button for audio player, since there was no way to close it while leaving other previewed content open.
  307. //• Fixed: Really, really fixed reload button and reload shortcut not working (I hope).
  308. //• Fixed: Yet more issues with arrow navigation.
  309. //• Fixed: Various UI issues with Firefox.
  310. //• Improved: Sidebar resizing should be more reliable (drag event was interacting with sidebar).
  311. //• Other: Abstracted some styles and cleaned up code.
  312. //• Many other bug fixes.
  313.  
  314. //• 2.6.3: Minor UI improvements and bugfixes.
  315. //• Changed: Open dirs on double-click. (You can still open them by selecting and pressing "Return" or the Right Arrow Key.)
  316. //• Changed: Removed autoplay_audio preference.
  317. //• Fixed: Reload button and reload shortcut weren't working correctly.
  318. //• Fixed: Clicking audio file name didn't pause/play file.
  319. //• Fixed: Sidebar height didn't reset when changing sort order.
  320. //• Fixed: Directories with a "." in the name were being sorted as if the trailing text was an extension.
  321. //• Fixed: Corrected some code that prevented the script from working in Safari; while Safari still doesn't allow local directories to be browsed, the script will work on server-generated pages.
  322. //• Other: Removed some unneeded styles and functions, simplified arrow navigation code.
  323.  
  324. //• 2.6.2: Fixes for a few more pesky bugs. Just when you think you're safe.... Apologies.
  325. //• Fixed: Reload and Close buttons and keyboard combinations now work for audio files.
  326. //• Fixed: Preview pane titlebar wasn't showing in some circumstances.
  327.  
  328. //• 2.6.1: Hopefully fixed a bug when navigating folders containing audio files. Sorry about that!
  329.  
  330. //• 2.6.0: Sorting, new user settings, and much more! This is a pretty substantial update.
  331. //• Added: Donate link (paypal.me/mschrauzer) to the main shortcuts menu. If you like this script, please support its development. Thank you!
  332. //• Added: Support for additional common server-generated index types. The script should now work on index pages structured as unordered lists, "<pre>" text, and html tables.
  333. //• Added: Sorting by name, size, date modified, kind, file extension, and default Chrome sorting (i.e., dirs on top, files alphabetical).
  334. //• Click the "Details" button (or Cmd/Ctrl + D) to show the available sort options.
  335. //• Note: Some server configurations don't provide size or date information; on such sites, these sorting options will not be available.
  336. //• Note: Sorting by kind is based on the file extension, so it is rather coarse (at least for now); many non-image/audio/pdf files will simply be categorized as generic "Files".
  337. //• Added: Added "Sort by..." menu item in the main shortcuts menu.
  338. //• Added: New User Settings (gives you control over some behavior added in the last update):
  339. //• default_sort: Choose default sort method: by name, size, date, kind, extension, or Chrome default (dirs on top, files alphabetical).
  340. //• dirs_on_top: Choose whether directories should be sorted separately from files.
  341. //• autoload_index_files: If true (default false), autoload first index.[ext] file found in directory.
  342. //• autoload_media:
  343. //• If false (default), treat audio files like other files for navigation purposes. Use up and down arrows as normal, only show audio controls when an audio file is selected.
  344. //• (Note that when false, only one file in the directory at a time can be selected.)
  345. //• If true, autoload first audio file in directory (and cover art, if any) and navigate audio with left and right arrows, other files with up and down arrows. This was the previous behavior of the script.
  346. //• Added: Alert reminder to add your computer user account name to the settings.
  347. //• Enhancement: UI: Added flyout submenus to main shortcuts menu.
  348. //• Enhancement: Added .aiff, .ape, .m4a to recognized audio format types.
  349. //• Enhancement: Added .svg to recognized image format types (they now are shown in an <image> element instead of an <iframe>).
  350. //• Enhancement: Moved the user settings to top of the code for easier access.
  351. //• Changed: Since the browser can't tell if an mp4 file is video or audio only, all mp4 files will load in the main preview area, not the audio player.
  352. //• Fixed an issue with image zoom.
  353. //• Fixed an issue with previewed content not being removed when content of another type was previewed.
  354. //• Internals: The script now converts server-generated index pages to the default Chrome table structure. Conversion happens in memory, so performance should be improved.
  355. //• Todo: Better video file handling.
  356.  
  357. // 2.5.0
  358. // A whole bunch of small and not-so-small additions, changes, bug fixes, and enhancements.
  359. // Added: Automatically load /index.[ext] files in the preview pane, if one exists in the directory.
  360. // Added: Initial support for AMMPS index pages; added "localhost" to @include rules.
  361. // Added: New user setting for external (non-local) shortcuts. Don't delete this item when pasting in your exported settings.
  362. // Added: Show/Hide sidebar. Hidden sidebar can still be navigated by the arrow keys.
  363. // Added: (Shift +) Alt/Option + Left/Right Arrows to skip audio ±10 or 30 secs.
  364. // Added: Also use the Prev and Next arrows to navigate font previews.
  365. // Added: Checkboxes for video files. Video file handling is still a bit of mess when audio files are also present in the directory; I will address this in a future update.
  366. // Added: Images can now be scaled (instead of just zoomed by clicking) with the +/– buttons and the scale keybindings.
  367. // Improved: Better scaling overall for images, fonts, and grids (use geometric instead of arithmetic scaling -- duh!).
  368. // Improved: Auto-file selection from user file shortcut list. First file found is loaded; user file shortcuts take precedence over autoloading index files.
  369. // Enhancement: Reload button also resets size of scaled images, fonts, and grids.
  370. // Fixed: Some lingering issues with arrow navigation and audio files selection.
  371. // Please read the updated list of keybindings in the description.
  372.  
  373. // 2.4.2
  374. // More audio file enhancements:
  375. // Added: Checkboxes to play or skip specific audio files. (Such a seemingly small thing, such a pain to implement.)
  376. // Added: Start/stop play by clicking file name.
  377. // Added: Use Reload button and reload keybinding (Cmd/Ctr-R) to return audio to beginning of track.
  378. // Changed: Don't autoplay audio when using left and right arrows to navigate tracks.
  379. // Fixed: Various issues with shuffle play.
  380. // Fixed: Completely rewrote autoload cover art code so that it actually works.
  381. // Fixed: Ignored files weren't being ignored in list-based index pages.
  382. // Many other bug fixes.
  383. // Minor UI adjustments (e.g., better prev and next track buttons).
  384.  
  385. // 2.4.1
  386. // Added: Loop and shuffle playback for audio files.
  387. // Added: Wrap-around keyboard navigation for audio tracks.
  388. // Added: Default background image for audio directories containing no image files.
  389. // Fixed some scrollIntoView issues.
  390. // Fixed auto-cover image selection issue in Firefox.
  391. // Bug-fixes. Yep, bug-fixes.
  392.  
  393. // 2.4.0
  394. // Major update for audio files!
  395. // New: Play all audio files in a directory.
  396. // New: Autoload cover art files (if available) in the preview pane. The script will look in the directory for image files titled "cover.[ext]" or "front.[ext]"; if they are not found, it will load the first image file it finds.
  397. // New: Audio file keyboard controls: Left/Right Arrows to navigate tracks, ignoring other files in the directory. Up/Down Arrows to navigate and preview all non-audio files in the directory (e.g., cover art or lyrics). Space bar to pause/play. (Note: grid-view is available in directories containing audio files, but the left/right arrow keys will not select grid items.)
  398. // New user setting: autoplay audio files. Default is false, but if true, the first audio file will start playing as soon as the directory is loaded. Again, don't delete it when pasting in exported settings.
  399. // Some code cleanup and bugfixes.
  400.  
  401. // 2.3.0
  402. // Added: New user settings: default grid image and font sizes. Don't delete them when pasting in your old exported settings!
  403. // Added: Scale grid image items.
  404. // Improved: Better image zooming. Zoomed images now scroll to the area clicked.
  405. // Fixed: Keybinding to toggle invisibles wasn't working.
  406. // Fixed: Image zoom not working.
  407. // Fixed: Hovered background for grid items in dark mode.
  408. // Fixed: Font scaling in dual grids.
  409.  
  410. // 2.2.0
  411. // Added: Export settings menu item. This will make it easier to save your user settings before updating the script. (Remember, scripts running on local files cannot set cookies or use localStorage, which would make it possible to save user settings automatic.) Unfortunately, there is no mechanism for importing the settings: you'll still have to open the script editor and paste in the exported data.
  412. // Fixed: File extensions were case sensitive, so some valid files wouldn't load if the extension contained capital letters.
  413. // Fixed: File and Dir names were also being lowercased.
  414.  
  415. // v. 2.1.1
  416. // Fixed: Can't type "r" in font previews.
  417. // Other minor bug fixes.
  418.  
  419. // v. 2.1.0
  420. // Added: Toggle Default/Dark modes menu item while browsing current directory; mode reverts to saved setting on dir change.
  421. // Added: White background for images so that images with transparency are visible against the grey background
  422. // Removed trailing "/" from directory display names.
  423. // Fixed an issue where font and image grid display got confused about what content to display.
  424. // Fixed directory names display in dark mode in server pages that use lists instead of tables.
  425. // Better styling for grid button.
  426. // Better styling for grid items in dark mode.
  427. // Bug fixes.
  428. // Remember to copy your user settings before updating!
  429.  
  430. // v. 2.0.2
  431. // Various fixes suggested by "Zorg":
  432. // Dark mode now ignores html files (and media files), but inverts all plain text files. This solution is not perfect: there is a flash of inverted content when navigating from an html or media file to a non-html or media file; will look for a fix.
  433. // Fixed display of non-Latin characters in sidebar header.
  434. // Add support for linux "home" directory for user profile shortcuts
  435. // Made entire up directory button clickable.
  436.  
  437. // v. 2.0.1
  438. // Fixed wonky font preview scaling.
  439.  
  440. // v. 2.0
  441. // NEW! Not just for local directories anymore! Added support for *many* server-generated directory index pages (It's impossible to check every server configuration, needless to say, so it might not work in every case; let me know if you encounter problems.// NEW! Preview font files (otf, ttf, woff, woff2). Great for designers: preview fonts without installing them. Hint: To install previewed fonts, just type Cmd/Ctrl + O or right-click the link and save it to your fonts folder. Font preview pane is content-editable.
  442. // NEW! Preview font files (otf, ttf, woff, woff2). Great for designers: preview fonts without installing them. Hint: To install previewed fonts, just type Cmd/Ctrl + Shift + O or right-click the link and save it to your fonts folder. Font preview pane is content-editable.
  443. // Added: Now use the Left and Right arrow keys to navigate through images and fonts in a folder, skipping other files. Use up and down arrow keys to navigate all files normally.
  444. // Added: Basic dark mode user setting.
  445. // Added: Wrap-around keyboard navigation.
  446. // Added: Cmd/Ctr-D to toggle details.
  447. // Added: Cmd/Ctr-Shift-. and Cmd/Ctr-Shift-, to scale font previews text size.
  448. // Changed: Moved dynamically-added in-line styles to appended stylesheet.
  449. // Changed: Extensive refactoring of code for better separation of concerns. (More needs to be done.... Sigh.)
  450. // Removed: ScrollIntoView for Grid views. Scrolling didn't work reliably when the sidebar was also being scrolled. Will restore when bug fixed.
  451. // Many small bug fixes.
  452.  
  453. // v. 1.4
  454. // Added: Initial support for Firefox. Tested in Firefox 59, Waterfox 56.
  455. // Changed: Use SVG for menu icons
  456. // Changed: Code cleanup, reorganization
  457. // Renamed script to "Supercharged Local Directory File Browser"
  458.  
  459. // v. 1.3
  460. // Fixed: Keyboard navigation of ignored or invisible items fails if "Hide Invisibles" is toggled off after loading a directory.
  461. // Added: Also hide ignored files if "Hide Invisibles" is checked.
  462. // Added: Content reload button.
  463. // Changed: Reorganized settings to reduce confusion about how ignored files are treated.
  464.  
  465. // v. 1.2
  466. // Click to show menus instead of hover.
  467. // Added Cmd/Crl+Shift+O keybinding to open selected item in new window
  468. // Arrow navigation bugfixes
  469.  
  470. // ***** GENERAL SETUP ***** //
  471.  
  472. // ************************************ //
  473. // DON'T EDIT ANYTHING BELOW THIS LINE. //
  474. // ************************************ //
  475.  
  476. // Styles for iFrame Directory Index pages
  477. if ( ( window.frameElement !== null || window.top != window.self) && window.location.href.endsWith('/') ) {
  478. const $custom_iframe_styles = document.createElement("style");
  479. var $iframe_styles = '';
  480.  
  481. $iframe_styles += 'a.up' +
  482. '{ background:transparent; }';
  483. $iframe_styles += ':root, html, body' +
  484. '{ background-color:#BBB; }';
  485. $iframe_styles += 'body > table > thead, body > table > tbody > tr, body ul li' +
  486. '{ background:rgba(221,221,221,0.5); }';
  487. $iframe_styles += 'body > table > tbody > tr:nth-of-type(odd), body > ul > li:nth-of-type(odd)' +
  488. '{ background:rgba(221,221,221,1); }';
  489. $iframe_styles += 'body > table > tbody > tr:hover, body > ul > li:hover:not(:first-of-type)' +
  490. '{ background:#ABABAB; }';
  491.  
  492. $iframe_styles += ':root, html, body' +
  493. '{ border:0 !important; }';
  494. $iframe_styles += 'body > table' +
  495. '{ border-top:solid 1px #888888; }';
  496. $iframe_styles += 'body > ul > li:first-of-type' +
  497. '{ border-bottom:solid 1px #888888; }';
  498. $iframe_styles += 'html, body' +
  499. '{ border-radius:0; }';
  500. $iframe_styles += 'body > table' +
  501. '{ border-collapse:collapse; }';
  502. $iframe_styles += 'html, body' +
  503. '{ box-sizing:border-box; }';
  504.  
  505. $iframe_styles += '#parentDirLinkBox span' +
  506. '{ color:transparent; }';
  507. $iframe_styles += 'a:hover' +
  508. '{ color:#000000; }';
  509. $iframe_styles += 'a, #parentDirLinkBox span:before' +
  510. '{ color:#111111; }';
  511.  
  512. $iframe_styles += '#parentDirLinkBox span:before' +
  513. '{ content:"Parent Directory"; }';
  514.  
  515. $iframe_styles += '#UI_goUp a.up:before' +
  516. '{ display:none; }';
  517. $iframe_styles += 'a, li' +
  518. '{ display:block; }';
  519. $iframe_styles += '#UI_goUp, #UI_showHidden' +
  520. '{ display:inline-table; }';
  521.  
  522. $iframe_styles += 'body > table table a:before' +
  523. '{ float:left; }';
  524.  
  525. $iframe_styles += ':root, html, body, *' +
  526. '{ font-family:'+ $settings.UI_font +'; }';
  527. const $font_size_units = $settings.UI_font_size.replace(/\d*/,'');
  528. $iframe_styles += 'html, body' +
  529. '{ font-size:'+ parseFloat($settings.UI_font_size) * 0.875 + $font_size_units +'; }';
  530. $iframe_styles += 'body > div, body > table, thead, body p' +
  531. '{ font-size: 1em; }';
  532. $iframe_styles += 'body > ul > li:first-of-type, #parentDirLinkBox span:before, #UI_goUp a' +
  533. '{ font-weight:bold; }';
  534.  
  535. $iframe_styles += 'html' +
  536. '{ height:100%; }';
  537. $iframe_styles += 'body' +
  538. '{ height:auto; }';
  539. $iframe_styles += '#UI_goUp, #UI_showHidden, #UI_showHidden label' +
  540. '{ height:1.5em !important; }';
  541.  
  542. $iframe_styles += 'body > table > tbody > tr, body > ul > li' +
  543. '{ line-height:1.5; }';
  544. $iframe_styles += '#UI_goUp, #UI_showHidden' +
  545. '{ line-height:2; }';
  546.  
  547. $iframe_styles += 'body > ul' +
  548. '{ list-style:none; }';
  549.  
  550. $iframe_styles += 'html, body, body div, body > ul > li a, #UI_goUp, #UI_showHidden, #parentDirLink, #parentDirLinkBox' +
  551. '{ margin:0; }';
  552. $iframe_styles += 'body > div' +
  553. '{ margin:1em; }';
  554. $iframe_styles += 'body p' +
  555. '{ margin:1rem; }';
  556. $iframe_styles += 'body > ul > li' +
  557. '{ margin-bottom:0; }';
  558. $iframe_styles += '.up' +
  559. '{ margin-inline-start:0; }';
  560. $iframe_styles += '.dir::before, .file > img' + // firefox
  561. '{ margin-inline-end: 6px; }';
  562.  
  563. $iframe_styles += ':root, html, body, body > div, body > ul > li, .up' +
  564. '{ padding:0; }';
  565.  
  566. $iframe_styles += 'body > table > tbody > tr > td, body > table > thead > tr > th:first-of-type, body > ul > li a, #parentDirLinkBox, #UI_goUp, #UI_showHidden' +
  567. '{ padding-top:3px; }';
  568. $iframe_styles += 'body > table > thead th' +
  569. '{ padding-top:6px; }';
  570.  
  571. $iframe_styles += 'body > table > tbody > tr > td:first-of-type, body > table > thead > tr > th:first-of-type, body > ul > li a' +
  572. '{ padding-right:0; }';
  573. $iframe_styles += 'body > table > tbody > tr > td:last-of-type, body > ul > li a, #parentDirLinkBox, #UI_showHidden' +
  574. '{ padding-right:1em; }';
  575.  
  576. $iframe_styles += '#parentDirLinkBox' +
  577. '{ padding-bottom:0; }';
  578. $iframe_styles += 'body > table > tbody > tr > td, body > table > thead > tr > th:first-of-type, body > ul > li a, #parentDirLinkBox, #UI_goUp, #UI_showHidden' +
  579. '{ padding-bottom:3px; }';
  580.  
  581. $iframe_styles += 'body > ul' +
  582. '{ padding-left:0; }';
  583. $iframe_styles += 'body > table > tbody > tr > td:first-of-type, body > table > thead > tr > th:first-of-type, body > ul > li a, #parentDirLinkBox, #UI_goUp' +
  584. '{ padding-left:1em; }';
  585.  
  586. $iframe_styles += 'th:first-of-type' +
  587. '{ text-align:left; }';
  588. $iframe_styles += 'a, a:hover, td:hover, li:hover' +
  589. '{ text-decoration:none !important; }';
  590. $iframe_styles += 'a.icon' +
  591. '{ text-indent:2px; }';
  592.  
  593. $iframe_styles += '#UI_goUp, #UI_showHidden' +
  594. '{ vertical-align:middle; }';
  595.  
  596. $iframe_styles += 'html, body, body > table' +
  597. '{ width:100%; }';
  598. $iframe_styles += 'thead th:first-of-type' +
  599. '{ width:50%; }';
  600. $iframe_styles += 'html, body' +
  601. '{ max-width:100%; }';
  602. $iframe_styles += 'html, body' +
  603. '{ min-width:100%; }';
  604. $iframe_styles += 'body table tr, body ul li, body > table > tbody > tr:hover' +
  605. '{ outline:0; }';
  606.  
  607. $('body').css({'font-family':$settings.UI_font});
  608. $('body > h1, address').remove();
  609. $('body > table > tbody').find('hr').closest('tr').remove();
  610. $('body > table th:contains("Name")').css({'text-align':'left'});
  611. $('body > table th:contains("Size")').css({'text-align':'right'});
  612. $custom_iframe_styles.appendChild(document.createTextNode(""));
  613. $custom_iframe_styles.append($iframe_styles);
  614. document.head.appendChild($custom_iframe_styles);
  615.  
  616. return;
  617. }
  618.  
  619. // Experimental: Don't run script in previewed contentEditable text and html files.
  620. if ( window.location.pathname.slice(-1) != '/') {
  621. // $('body').attr('contentEditable','true');
  622. return;
  623. }
  624.  
  625. const $userAgent = navigator.userAgent;
  626.  
  627. // PATHS
  628. var $href = decodeURIComponent(window.location.href); // complete URL including query string: http://www.host.com/path/to/my_dir/?query_string
  629. var $location = decodeURIComponent( [location.protocol, '//', location.host, location.pathname].join('') );
  630. var $location_arr = $location.split('/');
  631. var $current_dir_path = $location.replace(/\//g,'/<wbr>').replace(/_/g,'_<wbr>').replace(/—/g,'—<wbr>').replace(/\\/g,'/'); // URL w/o query string for display
  632. var $current_dir_name = $location.replace(/%20/g,' ').slice(0,-1);
  633. $current_dir_name = $current_dir_name.slice($current_dir_name.lastIndexOf('/') + 1); // Dir name without parents for display: "my_dir"
  634.  
  635. // QUERY PREFS
  636. var getQueryPrefs = function() { return new URLSearchParams( window.location.search ); };
  637. var $query_prefs = getQueryPrefs();
  638. var $UI_pref_width = $query_prefs.get('width') === null ? '25' : (Math.round(100*($query_prefs.get('width'))/window.innerWidth)).toString(); // number string
  639. var $UI_pref_theme = $query_prefs.get('dark_theme') === null ? $settings.dark_theme : JSON.parse( $query_prefs.get('dark_theme') ); // bool
  640. var $UI_pref_background = $query_prefs.get('alternate_background') === null ? $settings.alternate_background : JSON.parse( $query_prefs.get('alternate_background') ); // bool
  641. var $UI_pref_details = $query_prefs.get('hide_details') === null ? $settings.hide_details : JSON.parse( $query_prefs.get('hide_details') );
  642. var $UI_pref_sort = $query_prefs.get('sort') === null ? $settings.default_sort.toLowerCase() : $query_prefs.get('sort');
  643. var $UI_pref_file = $query_prefs.get('file') === null ? '' : $query_prefs.get('file');
  644. var $UI_pref_autoload_media = $query_prefs.get('autoload_media') == null ? $settings.autoload_media : JSON.parse( $query_prefs.get('autoload_media') );
  645. // var $UI_pref_invisibles = $query_prefs.get('hide_invisibles') == null ? $settings.hide_invisibles.toLowerCase() : $query_prefs.get('hide_invisibles');
  646. var $UI_pref_selected = $query_prefs.get('selected') === null ? '' : JSON.parse( $query_prefs.get('selected') );
  647. var $UI_pref_history = $query_prefs.get('history') === null ? '' : $query_prefs.get('history');
  648. var $UI_pref_history_arr;
  649. var $grid_image_size = $settings.grid_image_size ? $settings.grid_image_size : 150;
  650. var $grid_font_size = $settings.grid_font_size ? $settings.grid_font_size : 1;
  651. // set query key/value
  652. function setQuery(key, value) {
  653. $query_prefs = getQueryPrefs();
  654. $query_prefs.set( key, value );
  655. updateQuery();
  656. }
  657. // toggle query key
  658. function toggleQuery(key) {
  659. var value;
  660. $query_prefs = getQueryPrefs();
  661. value = $query_prefs.has(key) ? JSON.parse( $query_prefs.get(key) ) : $settings[key];
  662. value === true ? $query_prefs.set( key, 'false' ) : $query_prefs.set( key, 'true' );
  663. updateQuery();
  664. }
  665. // update query string
  666. function updateQuery() {
  667. $query_prefs = decodeURIComponent($query_prefs);
  668. window.history.replaceState({}, document.title, window.location.pathname +'?'+ $query_prefs);
  669. }
  670.  
  671. // Globals
  672. var e, i, j, n;
  673.  
  674. // ***** SET UP UI ELEMENTS ***** //
  675.  
  676. const $body = $('body');
  677. $body.attr('lang','en').find('> h1:contains("Index of"),> #parentDirLinkBox,> #UI_goUp,#UI_showHidden').remove();
  678. $('head').prepend('<meta charset="utf-8">');
  679.  
  680. // ***** SIDEBAR ELEMENTS ***** //
  681. const $parent_dir_menu = $('<nav id="parent_dir_menu"><a href="">&nbsp;</a></nav>');
  682. const $parents_dir_menu = $('<nav id="parents_dir_menu"><div></div></nav><ul class="menu"></ul>');
  683. const $shortcuts_menu = $('<nav id="shortcuts_menu"><div>&nbsp;</div></nav><ul id="shortcuts" class="menu"></ul>');
  684. const $details_btn = $('<button id="details_btn" tabindex="-1"><span>Show details</span><span>Hide details</span></button>');
  685. const $inv_checkbox = $('<label ><input type="checkbox" id="inv_checkbox" for="inv_checkbox" name="inv_checkbox" tabindex="-1" />Hide Invisibles</label>');
  686. 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>');
  687. 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>');
  688. 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>');
  689. const $dir_list_body = $('<tbody id="tbody"></tbody>');
  690. const $dir_list = $('<table id="dir_list"></table>');
  691. const $dir_list_wrapper = $('<div id="dir_list_wrapper"></div>');
  692. const $sidebar = $('<div id="sidebar"></div>');
  693. const $handle = $('<div id="handle"></div>');
  694. const $sidebar_wrapper = $('<td id="sidebar_wrapper"></td>');
  695. const $toggle_sidebar = $('<div id="toggle_sidebar"></div>');
  696.  
  697. // ***** CONTENT PANE ELEMENTS ***** //
  698. const $sidebar_overlay = $('<div id="sidebar_overlay"></div>');
  699. const $content_overlay = $('<div id="content_overlay"></div>');
  700. const $content_grid = $('<div id="content_grid" data-grid-scale-factor="0" data-kind="grid"></div>');
  701. const $image_grid_item_el = $('<div class="image_grid_item"><a href=""><img src="/" /></a></div>');
  702. const $font_grid_item_el = $('<div class="font_grid_item"></div>');
  703. const $content_scale = $('<div id="scale"><span id="increase"></span><span id="decrease"></span></div>');
  704. const $sample_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789 [(!@#$%^&*;:)]';
  705. 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.';
  706. 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>');
  707. const $content_font = $('<div id="content_font" spellcheck="false" contenteditable="true" data-kind="font"></div>');
  708. const $image = $('<img class="" data-kind="image" />');
  709. const $content_image = $('<div id="content_image" data-image-scale-factor="1" data-kind="image"></div>');
  710. const $content_pdf = $('<embed id="content_pdf" name="plugin" type="application/pdf" tabindex="0" data-kind="pdf"></embed>');
  711. const $content_iframe = $('<iframe id="content_iframe" sandbox="allow-scripts allow-same-origin allow-modals" tabindex="0" data-kind="file"></iframe>');
  712. const $content_video = $('<video id="content_video" class="media" controls data-kind="video">Your browser does not support the video tag.</video>');
  713. const $content_audio_title = $('<tr id="content_audio_title"><td colspan="3"></td></tr>');
  714. const $content_audio = $('<tr id="content_audio"><td colspan="3"></td></tr>');
  715. const $prev_track = $('<div id="prev_track" class="prev_next_track_btn" title="Previous track"></div>');
  716. const $next_track = $('<div id="next_track" class="prev_next_track_btn" title="Next track"></div>');
  717. const $audio = $('<audio id="audio" preload="auto" tabindex="0" controls>Sorry, your browser does not support HTML5 audio.</audio>');
  718. const $loop = $('<label><input type="checkbox" id="loop" for="loop" name="loop" tabindex="0" />Loop</label>');
  719. const $shuffle = $('<label><input type="checkbox" id="shuffle" for="shuffle" name="shuffle" tabindex="0" />Shuffle</label>');
  720. const $close_audio = $('<div id="close_audio" title="Close audio"></div>');
  721. const $checkbox_cont = $('<div id="checkbox_div"></div>');
  722. const $content_reload_btn = $('<td><button id="reload_btn" tabindex="-1">Reload</button></td>');
  723. const $content_stop_btn = $('<td><button id="stop" tabindex="-1">Stop</button></td>');
  724. const $content_title = $('<tr id="content_title"><td id="title"></td></tr>');
  725. const $content_close_btn = $('<td><button id="close_btn" tabindex="-1">Close</button></td>');
  726. const $content_header = $('<header id="content_header"><table><tbody></tbody></table></header>');
  727. const $prev_btn = $('<div class="nav_btn" id="prev_btn"></div>');
  728. const $next_btn = $('<div class="nav_btn" id="next_btn"></div>');
  729. const $content_container = $('<section id="content_container"></section>');
  730. const $content_pane = $('<td id="content_pane"></td>');
  731.  
  732. // SVG UI ICONS
  733. 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\' ';
  734. 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>")';
  735. 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>")';
  736. 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>")';
  737. 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>")';
  738. 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>")';
  739. 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>")';
  740. 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>")';
  741. 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>")';
  742. 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>")';
  743. 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>")';
  744. 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>")';
  745. 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>")';
  746. 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>")';
  747. 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>")';
  748. 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>")';
  749. 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>")';
  750. //SVG FILE ICONS
  751. // Chrome default icons
  752. 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= ")';
  753. const $file_icon_file_default = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
  754. // Custom file icons
  755. 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\'> ';
  756. 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> ")';
  757. 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> ")';
  758. 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> ")';
  759. 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> ")';
  760. 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> ")';
  761. 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> ")';
  762. 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> ")';
  763. 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>")';
  764. 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>")';
  765. 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>")';
  766. 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>")';
  767. 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>")';
  768. 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>")';
  769. // 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>")';
  770. // 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>")';
  771. // 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>")';
  772.  
  773.  
  774. // Assemble UI Elements
  775. function assembleUIElements() {
  776. $parents_dir_menu.find('div').append( $current_dir_path );
  777. $sidebar_header.find('thead th').append($toggle_sidebar);
  778. $sidebar_header.find('tbody tr').first().find('td').first().append( $parent_dir_menu ).next().append( $parents_dir_menu ).next().append( $shortcuts_menu );
  779. $sidebar_header.find('tbody tr:last-child td').append( $details_btn, $inv_checkbox, $grid_btn );
  780.  
  781. $dir_list.append($dir_list_head, $dir_list_body);
  782. $sidebar.append($sidebar_header, $dir_list);
  783. $sidebar_wrapper.append( $sidebar, $sidebar_overlay, $handle );
  784.  
  785. $content_image.append( $image );
  786. $checkbox_cont.append( $loop, $shuffle );
  787. $content_audio.find('td').append( $prev_track, $next_track, $audio, $close_audio, $checkbox_cont );
  788. // $content_audio_title.find('td').after( $close_audio );
  789. $content_title.prepend($content_reload_btn).append($content_close_btn);
  790. $content_header.find('tbody').append( $content_title, $content_audio_title, $content_audio );
  791. $content_font.append( $specimen );
  792. $content_container.append( $content_header, $content_overlay, $content_image, $content_grid, $content_font, $content_pdf, $content_video, $content_iframe, $prev_btn, $next_btn, $content_scale );
  793. $content_pane.append( $content_container );
  794. }
  795. assembleUIElements();
  796.  
  797. //***** STYLES *****//
  798.  
  799. // DEFINE STYLES
  800. var $styles = '';
  801.  
  802. // background-color
  803. $styles += '#sidebar_wrapper, body.light_theme #sidebar, body.light_theme #sidebar ul, body.light_theme #content_header' +
  804. '{ background-color: lightgray }';
  805. $styles += 'body.light_theme #dir_list .selected, body.light_theme #dir_list .playing' + //body.light_theme #dir_list .selected.audio,
  806. '{ background-color: lightsteelblue; }';
  807. $styles += 'body.dark_theme #dir_list .selected' +
  808. '{ background-color: slategray; }';
  809. $styles += 'body.dark_theme #content_grid, #content_image, body.dark_theme #content_pane' +
  810. '{ background-color: #333; }';
  811. $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)' +
  812. '{ background-color: #444; }';
  813. $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' +
  814. '{ background-color: #555; }';
  815. $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' +
  816. '{ background-color: #666; }';
  817. $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' +
  818. '{ background-color: #777; }';
  819. $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' +
  820. '{ background-color: #BBB; }';
  821. $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)' +
  822. '{ background-color: #CCC; }';
  823. $styles += 'body.light_theme #content_grid div:hover, body.light_theme #content_grid div.hovered' +
  824. '{ background-color: #DDD; }';
  825. $styles += 'body.light_theme #dir_list tbody' +
  826. '{ background-color: #DFDFDF; }';
  827. $styles += 'body.light_theme #content_pane, #content_image .img, body.light_theme #content_grid, #content_font, #content_iframe' +
  828. '{ background-color: #FFF; }';
  829. // EXCLUDED: Prevent previewed files with these extensions from being inverted in dark mode
  830. for ( let i = 0, n = $row_settings.exclude.length; i < n; i += 1) {
  831. $styles += 'body.dark_theme #content_iframe[src*="'+ $row_settings.exclude[i] +'" i] { background:#FFF; filter: unset; }';
  832. }
  833. $styles += 'body.dark_theme #content_iframe[src$="/" i]' +
  834. '{ background:#AAA; filter:unset; }';
  835. $styles += '#scale' +
  836. '{ background-color: rgba(128,128,128,0.3); }';
  837. $styles += 'body.is_chrome #audio' +
  838. '{ background-color: rgb(241, 243, 244); }';
  839. $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,
  840. '{ background-color: #4C7E80; }';
  841. $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' +
  842. '{ background-color: transparent; }';
  843.  
  844. // BACKGROUND SVG IMAGES & ICONS
  845. $styles += '#parent_dir_menu a { background:' + $up_arrow + 'center no-repeat; }';
  846. $styles += '#shortcuts_menu div { background:'+ $menu_icon + 'center no-repeat; }';
  847. $styles += 'body.light_theme #shortcuts > li.has_submenu { background:'+ $menu_arrow +' right no-repeat; background-size: 12px; }';
  848. $styles += 'body.light_theme #shortcuts > li.has_submenu:hover { background:#BBB '+ $menu_arrow +' right no-repeat; background-size: 12px; }';
  849. $styles += 'body.light_theme #shortcuts li.checked span { background:'+ $check_mark +' 4px center no-repeat; background-size:12px; }';
  850. $styles += '#grid_btn { background:'+ $grid_icon +' center no-repeat; }';
  851. $styles += '#toggle_sidebar { background:'+ $toggle +' center no-repeat; background-size:12px; }';
  852. $styles += '#dir_list thead th.checked { background:'+ $check_mark +' 10px 2px no-repeat; background-size:10px; }';
  853. $styles += 'body.light_theme #dir_list thead th.checked:after { background:'+ $up_arrow +' center no-repeat; background-size:10px; }';
  854. $styles += '#dir_list tbody a { background-size:auto 13px; background-position:6px 4px; }';
  855. $styles += 'body.has_audio #content_pane { background-image: ' + $music +'; background-position: center; background-repeat: no-repeat; background-size:33.33%; }';
  856. $styles += '#prev_track, #next_track { background: rgb(241, 243, 244) '+ $next_track_arrow +' center no-repeat; background-blend-mode:difference; }';
  857. $styles += '#prev_track:hover, #next_track:hover, #close_audio:hover { background-blend-mode:normal; }';
  858. $styles += '#close_audio { background:rgb(241, 243, 244); }';
  859. $styles += '#close_audio:after { background:'+ $plus_sign +' center no-repeat; background-blend-mode:difference; background-size:14px; }';
  860. $styles += '#scale span:first-of-type { background:'+ $plus_sign +' center no-repeat; }';
  861. $styles += '#scale span:last-of-type { background:'+ $minus_sign +' center no-repeat; }';
  862. $styles += '#prev_btn, #next_btn { background: ' + $svg_arrow + ' center no-repeat; }';
  863. $styles += 'body #content_pane.has_ignored { background-image:'+ $file_icon_ignored +'; background-position: center; background-repeat: no-repeat; background-size:50%; }';
  864. // Dark theme
  865. $styles += 'body.dark_theme #content_pane.has_ignored { background-image:'+ $file_icon_ignored_inv +'; background-position: center; background-repeat: no-repeat; background-size:50%; }';
  866. $styles += 'body.dark_theme #shortcuts > li.has_submenu { background:'+ $menu_arrow_inv +' right no-repeat; background-size: 12px; }';
  867. $styles += 'body.dark_theme #shortcuts > li.has_submenu:hover { background:#666 '+ $menu_arrow_inv +' right no-repeat; background-size: 12px; }';
  868. $styles += 'body.dark_theme #shortcuts li.checked span { background:'+ $check_mark_inv +' 4px center no-repeat; background-size:12px; }';
  869. $styles += 'body.dark_theme #dir_list thead th.checked { background:'+ $check_mark_inv +' 10px 2px no-repeat; background-size:10px; }';
  870. $styles += 'body.dark_theme #dir_list thead th.checked:after { background:'+ $up_arrow_inv +' center no-repeat; background-size:10px; }';
  871. // Default File Icons
  872. $styles += 'body.use_default_icons #dir_list .dir a.icon { background:'+ $file_icon_dir_default + ' 6px 4px no-repeat; background-size:auto 13px; }';
  873. $styles += 'body.use_default_icons #dir_list .file a.icon { background:'+ $file_icon_file_default + ' 6px 4px no-repeat; background-size:auto 13px; }';
  874. // Custom File Icons
  875. $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; }';
  876. $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; }';
  877. $styles += 'body.use_custom_icons #dir_list tr.file.font a.icon { background: '+ $file_icon_font +' 6px 4px no-repeat; background-size:14px 14px; }';
  878. $styles += 'body.use_custom_icons #dir_list tr.file.image a.icon { background: '+ $file_icon_image +' 6px 4px no-repeat; background-size:14px 14px; }';
  879. $styles += 'body.use_custom_icons #dir_list tr.file.pdf a.icon { background: '+ $file_icon_pdf +' 6px 4px no-repeat; background-size:14px 14px; }';
  880. $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; }';
  881. $styles += 'body.use_custom_icons #dir_list tr.file.ignore a.icon { background: '+ $file_icon_ignored +' 6px 4px no-repeat; }';
  882. $styles += 'body.use_custom_icons #dir_list tr.file.invisible a.icon { background: '+ $file_icon_invisible +' 6px 4px no-repeat; background-size:14px 14px; }';
  883. $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; }';
  884. $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; }';
  885. $styles += 'body.use_custom_icons #dir_list tr.app a { background: '+ $file_icon_app +' 6px 4px no-repeat !important; background-size:14px 14px; }';
  886. // Plain text file icons
  887. for ( let i = 0, n = $row_types.text.length; i < n; i += 1) {
  888. $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 }';
  889. }
  890. // Code file icons
  891. for ( let i = 0, n = $row_types.code.length; i < n; i += 1) {
  892. $styles += 'body.use_custom_icons #dir_list tr.file.'+ $row_types.code[i] +' a.icon { background: '+ $file_icon_code +' 6px 4px no-repeat; background-size:14px 14px }';
  893. }
  894.  
  895. // border
  896. $styles += ':root, html, body, #sidebar_wrapper, #sidebar_header, #dir_list, #main_content, #content_pane, #content_pdf, #content_iframe' +
  897. '{ border: 0 !important; }';
  898. $styles += 'body.dark_theme ul' +
  899. '{ border: solid 1px #222; }';
  900. $styles += '#details_btn' +
  901. '{ border: solid 1px #333; }';
  902. $styles += 'body.light_theme #sidebar ul, body.light_theme .image_grid_item img' +
  903. '{ border: solid 1px gray; }';
  904. // border-top
  905. $styles += 'body.dark_theme #sidebar_header .menu, body.dark_theme #sidebar #grid_btn ul.menu' +
  906. '{ border-top:solid 1px #111; }';
  907. $styles += 'body.dark_theme #dir_list #tbody' +
  908. '{ border-top:solid 1px #222; }';
  909. $styles += 'body.light_theme #dir_list tbody, #grid_btn .menu, .font_grid_item:not(:first-of-type), .image_grid_item + .font_grid_item' +
  910. '{ border-top:solid 1px grey; }';
  911. $styles += '#sort_by' + // , tr.dir ~ tr:not(.dir,.invisible,.ignore)
  912. '{ border-top:solid 1px #999; }';
  913. $styles += 'body.dark_theme.is_dirs_on_top #dir_list tbody tr.dir + tr.file' +
  914. '{ border-top:solid 1px #CCC; }';
  915. // border-right
  916. $styles += '#parents_dir_menu + ul, #shortcuts_menu + ul, body.dark_theme #sidebar #grid_btn ul.menu' +
  917. '{ border-right:0 !important; }';
  918. $styles += 'body.dark_theme #parents_dir_menu, body.dark_theme #sidebar, body.dark_theme #sidebar #grid_btn .menu li' +
  919. '{ border-right:solid 1px #111; }';
  920. $styles += '#sidebar, body.light_theme #parents_dir_menu, #grid_btn .menu li, .image_grid_item' +
  921. '{ border-right:solid 1px grey; }';
  922. // border-bottom
  923. $styles += 'body.dark_theme #sidebar_header thead tr, body.dark_theme #sidebar_header tbody tr:first-of-type' +
  924. '{ border-bottom:solid 1px black; }';
  925. $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' +
  926. '{ border-bottom:solid 1px #111; }';
  927. $styles += 'body.dark_theme #content_header tr:first-of-type' +
  928. '{ border-bottom:solid 1px #333; }';
  929. $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' +
  930. '{ border-bottom:solid 1px grey; }';
  931. $styles += '#content_header tr:first-of-type' +
  932. '{ border-bottom:solid 1px #888; }';
  933. $styles += '#shortcuts_menu + ul > li.has_submenu.ruled, body.is_dirs_on_top #dir_list tbody tr.sorted:not(:last-of-type), .rule' +
  934. '{ border-bottom:solid 1px #999; }';
  935. $styles += 'body.light_theme #content_header' +
  936. '{ border-bottom:solid 1px #AAA; }';
  937. $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' +
  938. '{ border-bottom:solid 1px #CCC; }';
  939. // border-left
  940. $styles += '#parents_dir_menu + ul, #shortcuts_menu + ul' +
  941. '{ border-left:0; }';
  942. $styles += 'body.dark_theme #parents_dir_menu, body.dark_theme #sidebar #grid_btn ul.menu' +
  943. '{ border-left:solid 1px #111; }';
  944. $styles += 'body.light_theme #parents_dir_menu, #grid_btn .menu' +
  945. '{ border-left:solid 1px grey; }';
  946.  
  947. // border-collapse
  948. $styles += 'table' +
  949. '{ border-collapse: collapse; }';
  950.  
  951. // border-radius
  952. $styles += 'html, body, :root' +
  953. '{ border-radius: 0; }';
  954. $styles += '#details_btn' +
  955. '{ border-radius: 3px; }';
  956.  
  957. // bottom
  958. $styles += '#handle, #parent_dir_menu, #sidebar_overlay, #content_overlay, #close_audio:after, #content_image, #content_pdf, #content_iframe, #content_grid, #content_font, #prev_btn, #next_btn, #dir_list tbody' +
  959. '{ bottom: 0; }';
  960.  
  961. // box-shadow
  962. $styles += 'body.light_theme #sidebar ul' +
  963. '{ box-shadow: 0px 2px 3px -2px #888; }';
  964. $styles += 'body.dark_theme #sidebar ul' +
  965. '{ box-shadow:0px 2px 3px -2px #111; }';
  966. $styles += 'body.dark_theme #sidebar #grid_btn ul.menu' +
  967. '{ box-shadow:none; }';
  968.  
  969. // box-sizing
  970. $styles += 'html, body, :root, #sidebar, #grid_btn .menu li' +
  971. '{ box-sizing: border-box; }';
  972.  
  973. // clear
  974. $styles += '#dir_list tbody .name' +
  975. '{ clear:right; }';
  976. $styles += '#checkbox_div label, #grid_btn .menu li, #dir_list tbody tr, #content_grid .font_grid_item' +
  977. '{ clear: both; }';
  978.  
  979. // columns
  980. $styles += '.lorem + .lorem' +
  981. '{ columns:2; column-gap:2em; }';
  982. $styles += '.lorem + .lorem + .lorem' +
  983. '{ columns:3; }';
  984.  
  985. // color
  986. $styles += 'body.dark_theme #shortcuts_menu + ul > li > ul, body.dark_theme #shortcuts_menu + ul > li.has_submenu + li:not(.has_submenu), #content_font' +
  987. '{ color: #111 }';
  988. $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' +
  989. '{ color: #111 }';
  990. $styles += 'body.dark_theme #sidebar_header thead tr, body.dark_theme #sidebar_header tbody tr:first-of-type' +
  991. '{ color: #444 }';
  992. $styles += '#scale span:hover' +
  993. '{ color: #666 }';
  994. $styles += '#dir_list tr.ignore a, #dir_list tr.ignore.app a, #dir_list tr.ignore td.details' +
  995. '{ color: #777 }';
  996. $styles += '#sort_menu .disabled span, #dir_list th.disabled' +
  997. '{ color: #999 }';
  998. $styles += 'body.dark_theme #dir_list tr.file.ignore a.icon, body.dark_theme #dir_list tr.ignore td.details' +
  999. '{ color: #BBB }';
  1000. $styles += 'body.dark_theme .font_grid_item p, body.dark_theme .font_grid_item a, body.dark_theme .image_grid_item p' +
  1001. '{ color: #DDD }';
  1002. $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' +
  1003. '{ color: #EEE }';
  1004.  
  1005. // content
  1006. $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' +
  1007. '{ content:"" }';
  1008. $styles += 'body.dark_theme #dir_list thead th.checked:after' +
  1009. '{ content:" " }';
  1010. $styles += '#content_pane.has_audio #content_audio_title td:before, body #content_pane.has_video #title:before' +
  1011. '{ content:"Playing: " }'; // .audio, .video
  1012. $styles += '#content_pane.has_dir:not(.has_grid) #title:before' +
  1013. '{ content:"Index of: " }'; // .dir
  1014. $styles += '#content_pane.has_grid:not(.has_ignored) #title:before' +
  1015. '{ content:"Fonts and Images from: " }'; // font and image grid
  1016. $styles += 'body:not(.has_images) #content_pane.has_grid:not(.has_ignored) #title:before, #content_pane.has_grid #title.font_grid:before' +
  1017. '{ content:"Fonts from: " }'; // font grid
  1018. $styles += 'body:not(.has_fonts) #content_pane.has_grid:not(.has_ignored) #title:before, #content_pane.has_grid #title.image_grid:before' +
  1019. '{ content:"Images from: " }'; // image grid
  1020. $styles += '#content_pane.has_ignored:not(.has_grid) #title:before' +
  1021. '{ content:"Ignored content: " }'; // .ignored
  1022.  
  1023. // cursor
  1024. $styles += '#sidebar_header thead th, #sort_menu .disabled span, #dir_list .disabled' +
  1025. '{ cursor: default; }';
  1026. $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' +
  1027. '{ cursor: pointer; }';
  1028. $styles += '#handle' +
  1029. '{ cursor: col-resize; }';
  1030. $styles += '#content_image img:not(.zoom_img)' +
  1031. '{ cursor: zoom-in; }';
  1032. $styles += '#content_image img.zoom_img' +
  1033. '{ cursor: zoom-out; }';
  1034.  
  1035. // display
  1036. $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, #sidebar_overlay, #content_overlay, #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.use_custom_icons #dir_list img, #dir_list tr.file.audio a img, #dir_list tr.file.video a img' +
  1037. '{ display: none; }';
  1038. $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[class^="has_"]: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' +
  1039. '{ display: block; }';
  1040. $styles += 'body.has_images.has_fonts #grid_btn:hover ul.menu' +
  1041. '{ display:block !important; }';
  1042. $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' +
  1043. '{ display: inline-block; }';
  1044. $styles += '#content_pane.has_audio #content_audio_title, #content_pane.has_audio #content_audio' +
  1045. '{ display: table-row !important; }';
  1046. $styles += '#shortcuts_menu div, #content_pane[class^="has"] #title, #content_pane[class^="has"] #content_header, #content_pane.has_audio #content_audio td' +
  1047. '{ display: table-cell !important; }';
  1048. $styles += '#content_pane.has_grid #content_grid.has_image_grid' +
  1049. '{ display: grid !important; }';
  1050. $styles += '#content_grid.has_image_grid' +
  1051. '{ grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(200px, 1fr)); align-content: start; }';
  1052. $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)' +
  1053. '{ display: table; }'; // needed to make rows full width and dir_list scrollable
  1054.  
  1055. // filter
  1056. $styles += '#prev_btn, #next_btn' +
  1057. '{ filter:invert(50%); }';
  1058. $styles += 'body.dark_theme #content_iframe, body.dark_theme #content_font' +
  1059. '{ filter:invert(87.5%); }';
  1060. $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' +
  1061. '{ filter:invert(100%); }';
  1062.  
  1063. // float
  1064. $styles += '#content_header tr:first-of-type td:first-of-type, #checkbox_div label' +
  1065. '{ float: left; }';
  1066. $styles += '#grid_btn, #content_header tr:first-of-type td:last-of-type' +
  1067. '{ float: right; }';
  1068.  
  1069. // font-family
  1070. $styles += 'html, body, :root' +
  1071. '{ font-family:'+ $settings.UI_font +', arial, "fira sans", helvetica, sans-serif; }';
  1072.  
  1073. // font-size
  1074. $styles += 'html' +
  1075. '{ font-size: 100.001%; }';
  1076. $styles += 'body, :root' +
  1077. '{ font-size:'+ $settings.UI_font_size +'; }';
  1078. $styles += '#sidebar, #sidebar_header, #sidebar_header thead th, #dir_list, #dir_list .details, #content_header, #content_header table' +
  1079. '{ font-size: 0.875rem; }';
  1080. $styles += '#content_grid, .font_grid_item p' +
  1081. '{ font-size: 1rem; }';
  1082. $styles += '#scale span' +
  1083. '{ font-size: 2rem; }';
  1084. $styles += '#content_grid .font_grid_item h2' +
  1085. '{ font-size:'+ $grid_font_size * 4 +'em; }';
  1086. $styles += '#content_font' +
  1087. '{ font-size:'+ $grid_font_size +'em; }';
  1088. $styles += '.lorem.first:first-line' +
  1089. '{ font-size:'+ $grid_font_size*1.33 +'em; }';
  1090.  
  1091. // font-variant
  1092. $styles += '.lorem.first:first-line' +
  1093. '{ font-variant: small-caps }';
  1094.  
  1095. // font-weight
  1096. $styles += '#sidebar_header thead th, h2' +
  1097. '{ font-weight: normal }';
  1098. $styles += '#dir_list .selected a, #dir_list .playing a' +
  1099. '{ font-weight: bold }';
  1100.  
  1101. // height
  1102. $styles += 'html, body, :root, #parent_dir_menu a, #content_container, #main_content, #content_pdf, #content_iframe, .image_grid_item a' +
  1103. '{ height: 100%; }';
  1104. $styles += '#sidebar_header, #parents_dir_menu, #parents_dir_menu div, #content_grid div img, #content_image img:not(.zoom_img)' +
  1105. '{ height: auto; }';
  1106. $styles += '#dir_list thead th.checked:after' +
  1107. '{ height: 8px; }';
  1108. $styles += '#toggle_sidebar' +
  1109. '{ height: 14px; }';
  1110. $styles += '#dir_list td.icon' +
  1111. '{ height: 16px; }';
  1112. $styles += '#grid_btn, body.use_custom_icons #dir_list tr.file.ignore a.icon::before' +
  1113. '{ height: 18px; }';
  1114. $styles += '#audio' +
  1115. '{ height: 32px; }';
  1116. $styles += 'body.dark_theme #dir_list thead th.checked:after' +
  1117. '{ height: 1em; }';
  1118. $styles += '#scale span' +
  1119. '{ height: 2rem; }';
  1120. $styles += '#content_grid.has_grid .image_grid_item' +
  1121. '{ height: '+ $grid_image_size +'px; }';
  1122. $styles += '#sidebar' +
  1123. '{ height:'+ window.innerHeight +'px; }';
  1124. $styles += '#sidebar_overlay, #content_overlay' +
  1125. '{ height:'+ window.innerHeight + 'px; }';
  1126. // max-height
  1127. $styles += '#content_image img.zoom_img' +
  1128. '{ max-height: none; }';
  1129. $styles += '#content_grid div img' +
  1130. '{ max-height:'+ ($grid_image_size - $grid_image_size/8) +'px; }';
  1131. $styles += '#content_image img:not(.zoom_img)' +
  1132. '{ max-height:calc(100% - 3px); }';
  1133. // min-height
  1134. $styles += '#sidebar' +
  1135. '{ min-height: 100%; }';
  1136.  
  1137. // hyphens
  1138. $styles += '#parents_dir_menu div, #content_header td button, #content_font' +
  1139. '{ hyphens: none; }';
  1140. $styles += 'html, body, :root, .lorem' +
  1141. '{ hyphens: auto; }';
  1142.  
  1143. // left
  1144. $styles += '#sidebar ul, #parent_dir_menu, #dir_list thead, #dir_list tbody, #sidebar_overlay, #content_overlay, #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' +
  1145. '{ left: 0; }';
  1146. $styles += '#sidebar #grid_btn ul.menu' +
  1147. '{ left: unset; }';
  1148. $styles += 'body.has_hidden_sidebar #sidebar_wrapper' +
  1149. '{ left: 3px; }';
  1150. $styles += '#shortcuts_menu + ul > li > ul' +
  1151. '{ left: 100%; }';
  1152.  
  1153. // letter-spacing
  1154. $styles += '.lorem.first:first-line, .font_grid_item p' +
  1155. '{ letter-spacing: 0.1em }';
  1156. $styles += '#sidebar_header thead th' +
  1157. '{ letter-spacing: 0.5em }';
  1158.  
  1159. // line-height
  1160. $styles += '.font_grid_item p' +
  1161. '{ line-height: 1 }';
  1162. $styles += '#dir_list tr a, .lorem' +
  1163. '{ line-height: 1.4 }';
  1164.  
  1165. // list-style
  1166. $styles += '#sidebar ul, #grid_btn .menu li' +
  1167. '{ list-style-type: none; }';
  1168.  
  1169. // margin
  1170. $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' +
  1171. '{ margin: 0; }';
  1172. // margin-top
  1173. $styles += '.image_grid_item + .font_grid_item' +
  1174. '{ margin-top:-1px; }';
  1175. $styles += '#checkbox_div label input, #details_btn' +
  1176. '{ margin-top: 0; }';
  1177. $styles += 'body.has_audio #dir_list input' +
  1178. '{ margin-top: 1px; }';
  1179. // margin-right
  1180. $styles += '#content_video' +
  1181. '{ margin-right: auto; }';
  1182. $styles += '#dir_list thead th.checked' +
  1183. '{ margin-right: 0; }';
  1184. $styles += 'body.has_audio #dir_list input' +
  1185. '{ margin-right: 8px; }';
  1186. $styles += '#details_btn' +
  1187. '{ margin-right: 0.5em; }';
  1188. $styles += '#dir_list thead th:last-of-type' +
  1189. '{ margin-right: 1em; }';
  1190. $styles += '#checkbox_div' +
  1191. '{ margin-right: calc(-6em - 4px); }';
  1192. // margin-bottom
  1193. $styles += '#details_btn' +
  1194. '{ margin-bottom: 0; }';
  1195. $styles += 'body.has_audio #dir_list input' +
  1196. '{ margin-bottom: 1px; }';
  1197. // margin-left
  1198. $styles += '#content_video' +
  1199. '{ margin-left: auto; }';
  1200. $styles += '#dir_list thead th.checked' +
  1201. '{ margin-left: 0; }';
  1202. $styles += 'body.has_hidden_sidebar #reload_btn' +
  1203. '{ margin-left: 16pt; }';
  1204. $styles += '#details_btn' +
  1205. '{ margin-left: 0.5em; }';
  1206. $styles += '#dir_list tbody tr' +
  1207. '{ margin-inline-start:0; }';
  1208. // -webkit-margin
  1209. $styles += '#sidebar ul' +
  1210. '{ -webkit-margin-before:0em !important; -webkit-margin-after:0em !important; -webkit-padding-start:0em; }';
  1211.  
  1212. // opacity
  1213. $styles += '#content_pane #scale' +
  1214. '{ opacity:0; }';
  1215. $styles += '#scale span:first-of-type, #scale span:last-of-type, #content_pane.has_ignored:before, #close_audio::after' +
  1216. '{ opacity:0.3; }';
  1217. $styles += '#scale span:hover' +
  1218. '{ opacity:0.5; }';
  1219. $styles += '#prev_btn, #next_btn, #close_audio:hover::after' +
  1220. '{ opacity:0.6; }';
  1221. $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' +
  1222. '{ opacity: 0.7; }';
  1223. $styles += '#content_grid div img' +
  1224. '{ opacity:0.8; }';
  1225. $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' +
  1226. '{ opacity:1; }';
  1227.  
  1228. // outline
  1229. $styles += '#grid_btn, #dir_list tbody, #dir_list tbody a, #content_grid .font_grid_item, #content_font' +
  1230. '{ outline: none; }';
  1231.  
  1232. // overflow
  1233. $styles += '#content_container' +
  1234. '{ overflow: visible; }';
  1235. $styles += 'html, body, :root, #parents_dir_menu div, #shortcuts_menu, #dir_list, #dir_list tbody a, #main_content, #close_audio' +
  1236. '{ overflow: hidden; }';
  1237. $styles += '#content_font, #content_grid, #content_image' +
  1238. '{ overflow: auto; }';
  1239. $styles += '#dir_list tbody' +
  1240. '{ overflow-y: auto; }';
  1241. $styles += '#sidebar, #content_font' +
  1242. '{ overflow-wrap: break-word; }';
  1243. $styles += '.lorem' +
  1244. '{ overflow-wrap: normal; }';
  1245.  
  1246. // padding
  1247. $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' +
  1248. '{ padding: 0; }';
  1249. $styles += '#sidebar_header thead th' +
  1250. '{ padding: 4px; }';
  1251. $styles += '.image_grid_item' +
  1252. '{ padding:6px; }';
  1253. // padding-top
  1254. $styles += '#dir_list thead th, #dir_list .details, #prev_track, #next_track, #close_audio' +
  1255. '{ padding-top: 0; }';
  1256. $styles += '#details_btn, #prev_btn, #next_btn' +
  1257. '{ padding-top: 2px; }';
  1258. $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' +
  1259. '{ padding-top: 4px; }';
  1260. $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' +
  1261. '{ padding-top: 6px; }';
  1262. $styles += '#content_header table' +
  1263. '{ padding-top: 7px; }';
  1264. $styles += '#content_grid .font_grid_item, .font_grid_item p' +
  1265. '{ padding-top: 0.5rem; }';
  1266. $styles += '#content_image, #content_font div' +
  1267. '{ padding-top: 2rem; }';
  1268. // padding-right
  1269. $styles += '#sidebar_header tbody tr:last-of-type td, #dir_list .details, #prev_track, #next_track, #close_audio' +
  1270. '{ padding-right: 0; }';
  1271. $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' +
  1272. '{ padding-right: 6px; }';
  1273. $styles += '#parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu + ul li div span' +
  1274. '{ padding-right: 8px; }';
  1275. $styles += '#dir_list tbody tr, #content_header table, #title, #prev_btn, #next_btn' +
  1276. '{ padding-right: 12px; }';
  1277. $styles += '#grid_btn .menu, #dir_list thead th, #content_audio_title td' +
  1278. '{ padding-right: 29px; }';
  1279. $styles += '#content_grid .font_grid_item, #content_image, #content_font div' +
  1280. '{ padding-right: 2.5rem; }';
  1281. // padding-bottom
  1282. $styles += '#prev_track, #next_track, #close_audio, #prev_btn, #next_btn' +
  1283. '{ padding-bottom: 0px }';
  1284. $styles += '#details_btn, body.is_dirs_on_top #dir_list tbody tr.sorted:not(:last-of-type)' +
  1285. '{ padding-bottom: 2px; }';
  1286. $styles += '#title, body #content_audio_title td' +
  1287. '{ padding-bottom: 3px;}';
  1288. $styles += '#parents_dir_menu div, #grid_btn .menu li, #dir_list tbody a, #dir_list .details' +
  1289. '{ padding-bottom: 4px; }';
  1290. $styles += '.specimen, .font_grid_item p' +
  1291. '{ padding-bottom: 0.25em; }';
  1292. $styles += '#content_header tr:first-of-type td:first-of-type, #content_header tr:first-of-type td:last-of-type' +
  1293. '{ padding-bottom: 3px; }';
  1294. $styles += '#content_header table' +
  1295. '{ padding-bottom: 5px; }';
  1296. $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' +
  1297. '{ padding-bottom: 6px; }';
  1298. $styles += '#dir_list thead th.details' +
  1299. '{ padding-bottom: 9px; }';
  1300. $styles += '#content_grid .font_grid_item' +
  1301. '{ padding-bottom: 0.5rem; }';
  1302. $styles += '#content_grid.has_image_grid' +
  1303. '{ padding-bottom: 1rem; }';
  1304. $styles += '#content_image, #content_font div:first-of-type' +
  1305. '{ padding-bottom: 2rem; }';
  1306. // padding-left
  1307. $styles += '#grid_btn .menu, #sidebar_header tbody tr:last-of-type td' +
  1308. '{ padding-left: 0; }';
  1309. $styles += '#dir_list .audio a, #dir_list .video a, #checkbox_div' +
  1310. '{ padding-left: 4px; }';
  1311. $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' +
  1312. '{ padding-left: 6px; }';
  1313. $styles += '#parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu + ul li div span' +
  1314. '{ padding-left: 8px; }';
  1315. $styles += '#content_header table, #dir_list td.size, #title, #prev_btn, #next_btn' +
  1316. '{ padding-left: 12px; }';
  1317. $styles += '#dir_list thead th, #dir_list .details, #dir_list td.date' +
  1318. '{ padding-left: 24px; }';
  1319. $styles += '#dir_list tbody a' +
  1320. '{ padding-left: 27px; -webkit-padding-start: 27px; }';
  1321. $styles += '#content_audio_title td' +
  1322. '{ padding-left: 29px; }';
  1323. $styles += '#sort_menu li span, #dir_list thead .name, #default' +
  1324. '{ padding-left: 2em; }';
  1325. $styles += '#content_grid .font_grid_item, #content_image, #content_font div' +
  1326. '{ padding-left: 2.5rem; }';
  1327. //-webkit-padding
  1328. $styles += '#dir_list .icon + td.name a' +
  1329. '{ -webkit-padding-start:1em; }';
  1330.  
  1331. // position
  1332. $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)' +
  1333. '{ position: relative; }';
  1334. $styles += '#sidebar ul, #handle, #parent_dir_menu, #toggle_sidebar, body.has_hidden_sidebar #sidebar_wrapper, #dir_list tbody, #dir_list thead th.checked:after, #sidebar_overlay, #content_overlay, #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' +
  1335. '{ position: absolute; }';
  1336.  
  1337. // right
  1338. $styles += '#sidebar ul, #parent_dir_menu, #grid_btn .menu, #dir_list thead, #dir_list tbody, #sidebar_overlay, #content_overlay, #close_audio:after, #content_header, #content_image, #content_pdf, #content_iframe, #content_grid, #content_font, #next_btn' +
  1339. '{ right: 0; }';
  1340. $styles += '#toggle_sidebar' +
  1341. '{ right: 4px; }';
  1342. $styles += '#handle' +
  1343. '{ right: -4px; }';
  1344. $styles += '#scale' +
  1345. '{ right: 1rem; }';
  1346.  
  1347. // table-layout
  1348. $styles += '#dir_list' +
  1349. '{ table-layout: fixed; }';
  1350.  
  1351. // text-align
  1352. $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' +
  1353. '{ text-align: left; }';
  1354. $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' +
  1355. '{ text-align: center; }';
  1356. $styles += '#grid_btn .menu li, #dir_list .size, #dir_list .date, #content_header tr:first-of-type td:last-of-type' +
  1357. '{ text-align: right; }';
  1358. $styles += '.lorem' +
  1359. '{ text-align: justify; }';
  1360.  
  1361. // text-decoration
  1362. $styles += 'a, a:hover' +
  1363. '{ text-decoration: none !important; }';
  1364.  
  1365. // text-indent
  1366. $styles += '#shortcuts li' +
  1367. '{ text-indent: 1em; }';
  1368.  
  1369. // text-transform
  1370. $styles += '.font_grid_item p' +
  1371. '{ text-transform: uppercase; }';
  1372.  
  1373. // top
  1374. $styles += '#handle, #parent_dir_menu, #content_container, #sidebar_overlay, #content_overlay, #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' +
  1375. '{ top: 0; }';
  1376. $styles += '#shortcuts_menu + ul > li > ul, body.light_theme #grid_btn .menu' +
  1377. '{ top: -1px !important; }';
  1378. $styles += 'body.has_hidden_sidebar #sidebar_wrapper, #dir_list thead th.checked:after' +
  1379. '{ top: 2px; }';
  1380. $styles += '#toggle_sidebar' +
  1381. '{ top: 4px; }';
  1382. $styles += 'body.dark_theme #grid_btn .menu' +
  1383. '{ top: -7px; }';
  1384. $styles += '#content_grid div img, #content_image img:not(.zoom_img), .vert_center' +
  1385. '{ top: 50%; }';
  1386.  
  1387. // transform
  1388. $styles += 'body.has_hidden_sidebar #toggle_sidebar, #dir_list thead th.checked:not(.up):after, #next_track, #next_btn' +
  1389. '{ transform:rotate(180deg); }';
  1390. $styles += '#close_audio:after' +
  1391. '{ transform:rotate(45deg); }';
  1392. $styles += '.vert_center' +
  1393. '{ transform:translateY(50%); }';
  1394. $styles += '#content_grid div img, #content_image img:not(.zoom_img)' +
  1395. '{ transform:translateY(-50%); }';
  1396. $styles += '#content_image img.zoom_img' +
  1397. '{ transform:translateY(0); }';
  1398.  
  1399. // transition
  1400. $styles += '#scale' +
  1401. '{ transition:opacity 1s ease-in-out; }';
  1402.  
  1403. // user-select
  1404. $styles += '#sidebar_header, #content_image img:not(.zoom_img)' +
  1405. '{ user-select:none; -webkit-user-select:none; }';
  1406.  
  1407. // vertical-align
  1408. $styles += '#dir_list .details' +
  1409. '{ vertical-align: top }';
  1410. $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' +
  1411. '{ vertical-align:middle; }';
  1412.  
  1413. // white-space
  1414. $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' +
  1415. '{ white-space: normal; }';
  1416. $styles += '#grid_btn .menu li' +
  1417. '{ white-space: pre; }';
  1418. $styles += '#dir_list thead' +
  1419. '{ white-space: pre-wrap; }';
  1420.  
  1421. // width
  1422. $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' +
  1423. '{ width: 100%; }';
  1424. $styles += '#content_grid, #content_grid div img, #content_image img:not(.zoom_img)' +
  1425. '{ width: auto; }';
  1426. $styles += '#handle' +
  1427. '{ width: 8px; }';
  1428. $styles += '#toggle_sidebar' +
  1429. '{ width: 16px; }';
  1430. $styles += 'body.use_custom_icons #dir_list tr.file.ignore a.icon::before' +
  1431. '{ width: 20px; }';
  1432. $styles += '#sidebar_header tbody tr:first-of-type td:nth-of-type(odd)' +
  1433. '{ width: 24px; }';
  1434. $styles += '#grid_btn' +
  1435. '{ width: 28px; }';
  1436. $styles += '#dir_list thead th.checked:after, #prev_track, #next_track, #close_audio, #scale span' +
  1437. '{ width: 2rem; }';
  1438. $styles += '#shortcuts_menu div, #content_header tr:first-of-type td:first-of-type, #content_header tr:first-of-type td:last-of-type, #checkbox_div' +
  1439. '{ width: 6em; }';
  1440. $styles += 'body.has_hidden_sidebar #sidebar_wrapper' +
  1441. '{ width: 0 !important; }';
  1442. $styles += '#dir_list .size, #dir_list .kind' +
  1443. '{ width: 16%; }';
  1444. $styles += '#dir_list .date' +
  1445. '{ width: 30%; }';
  1446. $styles += '#content_grid.has_grid .image_grid_item' +
  1447. '{ width: '+ $grid_image_size +'px; }';
  1448. $styles += 'body.has_hidden_sidebar #content_pane' +
  1449. '{ width: 100% !important; }';
  1450. $styles += 'body:not(.has_hidden_sidebar) #sidebar_wrapper' +
  1451. '{ width:'+ $UI_pref_width +'%; }';
  1452. $styles += '#content_pane' +
  1453. '{ width:'+ (100 - $UI_pref_width).toString() +'%; }';
  1454. $styles += '#content_grid .font_grid_item' +
  1455. '{ width:calc(100% - 5rem); }';
  1456. // max-width
  1457. $styles += 'html, body, :root, #content_image img:not(.zoom_img)' +
  1458. '{ max-width: 100%; }';
  1459. $styles += '#content_image img.zoom_img' +
  1460. '{ max-width: none; }';
  1461. $styles += '#sidebar_header tbody tr:first-of-type td:nth-of-type(odd)' +
  1462. '{ max-width: 24px; }';
  1463. $styles += '#content_grid div img' +
  1464. '{ max-width:'+ ($grid_image_size - $grid_image_size/8) +'px; }';
  1465. // min-width
  1466. $styles += 'body:not(.has_hidden_sidebar) #sidebar_header tbody tr:first-of-type td:nth-of-type(odd)' +
  1467. '{ min-width: 24px; }';
  1468. $styles += '#dir_list .size, #dir_list .kind' +
  1469. '{ min-width: 5em; }';
  1470. $styles += '#dir_list td.date' +
  1471. '{ min-width: 12em; }';
  1472. $styles += 'body:not(.has_hidden_sidebar) #dir_list' +
  1473. '{ min-width: 100px; }';
  1474. $styles += 'body:not(.has_hidden_sidebar) #sidebar_wrapper' +
  1475. '{ min-width: 220px; }';
  1476.  
  1477. // will-change
  1478. $styles += '#sidebar_wrapper, #content_pane' +
  1479. '{ will-change: width }';
  1480.  
  1481. // word-break
  1482. $styles += '#content_header td button' +
  1483. '{ word-break: none; }';
  1484. $styles += '#title' +
  1485. '{ word-break: break-word; }';
  1486. $styles += '#content_font' +
  1487. '{ word-break: break-all; }';
  1488. $styles += '.lorem' +
  1489. '{ word-break: normal; }';
  1490.  
  1491. // z-index
  1492. $styles += '#content_grid' +
  1493. '{ z-index: -1; }';
  1494. $styles += '#content_pane.has_grid #content_grid' +
  1495. '{ z-index: 1; }';
  1496. $styles += '#scale, #prev_btn, #next_btn' +
  1497. '{ z-index: 10; }';
  1498. $styles += '#parents_dir_menu + ul, #content_header' +
  1499. '{ z-index: 20; }';
  1500. $styles += '#handle' +
  1501. '{ z-index: 1000; }';
  1502. $styles += '#sidebar_header' +
  1503. '{ z-index: 2000; }';
  1504. $styles += '#shortcuts_menu + ul, #toggle_sidebar, #sidebar_overlay, #content_overlay' +
  1505. '{ z-index: 9999; }';
  1506. $styles += 'body.has_hidden_sidebar #sidebar_header' +
  1507. '{ z-index:unset; }';
  1508.  
  1509. // Gecko Styles:
  1510. var $gecko_styles = '';
  1511.  
  1512. $gecko_styles += 'html, body.is_gecko { border: solid 1px gray !important; }';
  1513. $gecko_styles += 'body.is_gecko button { padding:0; }';
  1514. $gecko_styles += 'body.is_gecko #grid_btn .menu { top:-7px; left:-120px; }';
  1515. $gecko_styles += 'body.is_gecko thead {font-size:100%;}';
  1516. $gecko_styles += 'body.is_gecko #dir_list th, body.is_gecko #dir_list td {width:auto;}';
  1517. $gecko_styles += 'body.is_gecko #dir_list .dir::before {position:absolute;}';
  1518. $gecko_styles += 'body.is_gecko.use_default_icons:not(.is_converted_list) #dir_list .file .name .icon { padding-left:4px; background:none; }';
  1519. $gecko_styles += 'body.is_gecko.use_default_icons #dir_list .file .name .icon img { margin-right:6px; height:14px; }';
  1520. $gecko_styles += 'body.is_gecko #tbody > tr > td:not(:first-of-type) { float:left }';
  1521. $gecko_styles += 'body.is_gecko #content_audio_title td { padding-top: 6px; }';
  1522. $gecko_styles += 'body.is_gecko #content_audio_title td { padding-bottom: 0; }';
  1523. $gecko_styles += 'body.is_gecko #prev_track { padding-left: 16px; }';
  1524. $gecko_styles += 'body.is_gecko #close_audio { background-color: #252525; }';
  1525. $gecko_styles += 'body.is_gecko #close_audio:after { filter:invert(100%); opacity:0.5; }';
  1526. $gecko_styles += 'body.is_gecko #close_audio:hover:after { opacity:0.75; }';
  1527. $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; }';
  1528. $gecko_styles += 'body.is_gecko #prev_track:hover, body.is_gecko #next_track:hover { background: #252525 '+ $next_track_arrow_gecko_hover +' center no-repeat; }';
  1529. $gecko_styles += 'body.is_gecko #content_title td:first-of-type, body.is_gecko #content_title td:last-of-type { padding-top:8px; }';
  1530.  
  1531. // ADD STYLES
  1532. const $font_preview_styles = document.createElement('style');
  1533. const $custom_styles = document.createElement("style");
  1534. const $custom_gecko_styles = document.createElement("style");
  1535.  
  1536. function addStyles() {
  1537. // for font previews
  1538. $font_preview_styles.appendChild(document.createTextNode(""));
  1539. document.head.append($font_preview_styles);
  1540. var $font_styles = '';
  1541. $font_preview_styles.append( $font_styles );
  1542. // basic UI styles
  1543. $custom_styles.appendChild(document.createTextNode(""));
  1544. $custom_styles.append($styles);
  1545. document.head.appendChild($custom_styles);
  1546. // Gecko styles
  1547. if ( $userAgent.indexOf('Firefox') > -1 ) {
  1548. $custom_gecko_styles.appendChild(document.createTextNode(""));
  1549. $custom_gecko_styles.append($gecko_styles);
  1550. document.head.appendChild($custom_gecko_styles);
  1551. }
  1552. }
  1553. addStyles();
  1554. // ***** END STYLES ***** //
  1555.  
  1556. // ***** BUILD MENUS ***** //
  1557. var $paths_arr = [];
  1558. const parentLinksArr = function() { // create array of parent links from $location w/o query string
  1559. let $path = $location; // https://www.example.com/path/to/my/dir/
  1560. while ( $path.lastIndexOf('/') > 0 && !$path.endsWith(':/') ) {
  1561. $path = $path.slice( 0,$path.lastIndexOf('/') );
  1562. $paths_arr.push($path);
  1563. }
  1564. $paths_arr.pop(); // remove last element
  1565. return $paths_arr;
  1566. };
  1567. parentLinksArr();
  1568.  
  1569. // MENUS: Create menu list items
  1570. var $menu_item;
  1571. const menuItems = function(x,y,i,b) { // (link, name, count, boolean)
  1572. let $qstr = '/?';
  1573. if ( b === true ) { // don't include query string in link
  1574. $qstr = '';
  1575. $query_prefs = '';
  1576. }
  1577. $menu_item = '<li><a href="'+ x[i] + $qstr + $query_prefs +'">' + y[i] + '/</a></li>';
  1578. return $menu_item;
  1579. };
  1580. // Parent directory link setup
  1581. var $parent_dir_link;
  1582. function setParentLink() {
  1583. let $query_prefs = new URLSearchParams( window.location.search );
  1584. $query_prefs.delete('file');
  1585.  
  1586. $UI_pref_history_arr = $query_prefs.get('history') !== null ? $query_prefs.get('history').split(' ') : [];
  1587.  
  1588. if ( $UI_pref_history_arr.length == 1 ) {
  1589. $query_prefs.set('selected',$UI_pref_history_arr.shift(0));
  1590. $query_prefs.delete('history');
  1591. } else if ( $UI_pref_history_arr.length > 1 ) {
  1592. $query_prefs.set('selected',$UI_pref_history_arr.shift(0));
  1593. $query_prefs.set('history',$UI_pref_history_arr.join('+'));
  1594. } else {
  1595. $query_prefs.delete('selected');
  1596. }
  1597. let $qstr = $query_prefs.toString().length == 0 ? '' : '?';
  1598. $query_prefs.sort();
  1599. $parent_dir_link = $location_arr.slice(0,-2).join('/') + '/' + $qstr + decodeURIComponent($query_prefs);
  1600.  
  1601. $parent_dir_menu.find('a').attr( 'href', $parent_dir_link );
  1602. }
  1603. setParentLink();
  1604. // Click parent directory menu button
  1605. $parent_dir_menu.on('click','a',function(e) {
  1606. e.preventDefault();
  1607. setParentLink(); // update link in case query string has changed
  1608. $('#parent_dir_menu').find('a').attr( 'href', $parent_dir_link );
  1609. window.location = $parent_dir_link;
  1610. });
  1611.  
  1612. // MENUS: Directory Parents Links
  1613. const parents_dir_menu_arr = function() {
  1614. var $parents_dir_menu_items = [];
  1615. // make array of directory parents menu items
  1616. for ( let i = 1, n = $paths_arr.length + 1; i < n; i+=1 ) {
  1617. $parents_dir_menu_items[i] = menuItems( $paths_arr, $paths_arr, i, false);
  1618. }
  1619. $parents_dir_menu_items.pop(); // remove current directory
  1620.  
  1621. if ( $parents_dir_menu_items[1] !== undefined ) {
  1622. let str = $parents_dir_menu_items[1].slice($parents_dir_menu_items[1].lastIndexOf('?'),$parents_dir_menu_items[1].lastIndexOf('">'));
  1623. for ( let i = 1; i < $parents_dir_menu_items.length; i+=1 ) { // recursively update selected and history query str
  1624. str = str.replace(/selected=\d+&*/,'').replace(/history=(\d+)$/,'selected=$1').replace(/history=(\d+)\+/,'selected=$1&history=').replace(/&$/,'');
  1625. $parents_dir_menu_items[i] = $parents_dir_menu_items[i].replace(/\?.+?">/,str +'">');
  1626. }
  1627. }
  1628. // cleanup links
  1629. $parents_dir_menu_items = $parents_dir_menu_items.join('')
  1630. .replace(/%20/g,' ').replace(/file=.+?&/g,'&').replace(/file=.+?/g,'').replace(/>\/(?!\/)/g,'>').replace(/>\/\/\/\/</,'>/<').replace(/>\/\/\/</,'>/<');
  1631. return $parents_dir_menu_items;
  1632. };
  1633.  
  1634. // MENUS: User Shortcuts
  1635. var $menu_items = [];
  1636. var $shortcuts_links_arr = [];
  1637.  
  1638. const shortcutMenus = function() {
  1639. const $shortcuts = $settings.shortcuts;
  1640.  
  1641. if ( $shortcuts.length > 0 ) {
  1642. for ( let i = 0; i < $shortcuts.length; i+=1 ) {
  1643. var $links_arr = [];
  1644. var $links = $shortcuts[i].links;
  1645. // make array of links
  1646. for ( let j = 0; j < $links.length; j+=1 ) {
  1647. if ( !$links[j].link.endsWith('/') ) {
  1648. $links[j].link = $links[j].link.slice(0,$links[j].link.lastIndexOf('/') + 1) + '?file=' + $links[j].link.slice($links[j].link.lastIndexOf('/') + 1) ;
  1649. }
  1650. $shortcuts_links_arr.push($links[j].link);
  1651. $links_arr[j] = '<li><a href="'+ $links[j].link +'">' + $links[j].link_name + '</a></li>';
  1652. }
  1653. var $links_arr_str = $links_arr.join('');
  1654. $menu_items[i] = '<li class="has_submenu"><a>'+ $shortcuts[i].menu_title +'</a><ul>'+ $links_arr_str +'</ul></li>';
  1655. }
  1656. $menu_items = $menu_items.join('');
  1657. }
  1658. return ($shortcuts_links_arr,$menu_items);
  1659. };
  1660. shortcutMenus();
  1661.  
  1662. // MENUS: Other menu items
  1663. 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>');
  1664. const $autoload_media = $('<li id="autoload_media" class="rule"><span id="autoload_media_menu">Autoload Media</span></li>');
  1665. 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>');
  1666. const $toggle_alt_bckgrd = $('<li class="rule" id="toggle_alternate_background_menu_item"><span id="alternate_background">Alternate Backgrounds</span></li>');
  1667. const $default_settings = $('<li><a href="#" id="default_settings" title="Delete URL query string and reload page.">Default User Settings</a></li>');
  1668. const $export_settings = $('<li class="rule"><a href="#" id="export_settings" title="Export user settings to text file.">Export User Settings</a></li>');
  1669. const $contact_link = $('<li><a id="contact" href="mailto:mshroud@vivaldi.net">Contact</a></li>');
  1670. const $donate_link = $('<li><a id="donate" href="https://paypal.me/mschrauzer" target="_blank">Donate</a></li>');
  1671.  
  1672. function assembleMenus() { // called by buildUI();
  1673. $parents_dir_menu.siblings('ul').append( parents_dir_menu_arr() );
  1674. $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 );
  1675. }
  1676. assembleMenus();
  1677.  
  1678. // ***** END BUILD MENUS ***** //
  1679. // ***** DIRECTORY LIST Setup ***** //
  1680.  
  1681. // CONVERT INDEX PAGES TO DEFAULT (Chrome) STRUCTURE
  1682.  
  1683. // Convert matched date and size strings to default size and date format, calculate data-values for sorting
  1684. var yr, mo, dd, pref;
  1685. var factor = {k:1, kb:1, m:1000, mb:1000, g:1000000, gb:1000000, t:1000000000,tb:1000000000}; // for file size conversion
  1686. var sizeCalc = function(x,y) { // x = number, y = byte size
  1687. y === '' ? y = 'k' : y = y;
  1688. return x == '-' ? 0 : x * factor[ y.toString().toLowerCase() ]; // convert byte size to multiplication factor
  1689. };
  1690. var sizeAndDate = function(match,p1,p2,p3,p4,p5,p6,offset,string) {
  1691. p1.toString().length == 4 ? (yr = p1, dd = p3) : (yr = p3, dd = p1);
  1692. p2.toString().length == 3 ? mo = "JanFebMarAprMayJunJulAugSepOctNovDec".indexOf(p2)/3 + 1 : mo = p2; // convert month into number, or use number
  1693. p6 = undefined|'' ? p6 = 1 : p6 = p6;
  1694. mo.toString().length == 1 ? pref = 0 : pref = null; // add a zero before single digit months (i.e., multiply year by 10)
  1695. 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';
  1696. };
  1697. // Standardize directory lists
  1698. function convertDirList() {
  1699. var $list_items;
  1700. // AMPPS index pages
  1701. let $protocol = window.location.protocol;
  1702. if ( $protocol.indexOf('localhost') > -1 && $body.find('> title:contains(AMPPS)').length ) {
  1703. $dir_list = $body.find('> center > div > table');
  1704. $dir_list.remove();
  1705. $body.addClass('is_ampps').empty().append($dir_list);
  1706. }
  1707. // Pre-type
  1708. if ( $protocol !== 'file:' && $body.find('> pre').length > 0 ) {
  1709. $list_items = $body.find('> pre').html();
  1710. $body.addClass('is_converted_pre').find('> pre').remove();
  1711.  
  1712. $list_items = $list_items
  1713. .replace(/^.+?\n/, '') // delete header row
  1714. .replace(/^.+?Parent Directory.+?\n/gm, '\n') // delete parent directory link
  1715. .replace(/^\s*<a.+?>\.\.\/<\/a>\s*$/gm, '') // delete parent directory link (list type)
  1716. .replace(/^\s*<img .+?>\s*/gm, '') // delete all images (icons)
  1717. .replace(/^\s*<address>.+?<\/address>\s*/,'') // delete address element
  1718. .replace(/^\s*<a href="(.+?)">(.+?)<\/a>\s*/gm, '<tr><td data-value="$2"><a class="icon" href="$1">$2</a></td>') // create name cell
  1719. .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
  1720. };
  1721. // List-type
  1722. if ( $protocol !== 'file:' && $body.find('> ul').length > 0 ) {
  1723. $list_items = $body.find('> ul').html();
  1724. $body.addClass('is_converted_list').find('> ul').remove();
  1725.  
  1726. if ( $list_items.indexOf('</a></li>') != -1 ) {
  1727. $list_items = $list_items.replace(/<li>\s*/g,'<tr><td data-value="">').replace(/\s*<\/li>/g, '</td></tr>\n');
  1728. $list_items = $list_items.replace(/data-value=""><a href="(.+?)">\s+(.+?)<\/a>/g, 'data-value="$2"><a class="icon" href="$1">$2<\/a>');
  1729. $list_items = $list_items.replace(/<tr.+?Parent Directory.+?<\/tr>/,'');
  1730. }
  1731. }
  1732. // Table-type
  1733. if ( $protocol !== 'file:' && $body.find('> table').length > 0 ) {
  1734. $list_items = $body.find('> table tbody').length > -1 ? $body.find('> table tbody').html() : $body.find('> table');
  1735. $body.addClass('is_converted_table').find('> table').remove();
  1736.  
  1737. $list_items = $list_items
  1738. // .replace(/<img .+?>/g, '') // delete all images (icons)
  1739. .replace(/<tr><th.+?\/th><\/tr>/g, '') // delete empty tr
  1740. .replace(/<tr><td.+?Parent Dir.+?<\/td><\/tr>/, '') // delete empty tr
  1741. .replace(/(?:<th>|<td>|<td \w+="\w+">)(?:&nbsp;|\s*)(?:<\/th>|<\/td>)/g, '') // delete empty th or td
  1742. .replace(/<td>\s*<a href="(.+?)">(.+?)<\/a>\s*<\/td>/g, '<td class="name" data-value="\L$2"><a class="icon" href="$1">$2</a></td>')
  1743. .replace(/<tr><td.*?><img (.+?)><\/td><td (.+?)><a (.+?)>(.+?)<\/a>/g, '<tr><td $2><a $3><img $1>$4</a>') // move default image to .name td
  1744. .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)
  1745. .replace(/^\s*<address>.+?<\/address>\s*$/,''); // delete address line
  1746. }
  1747. // Gecko-style local index pages
  1748. if ( $protocol === 'file:' && $userAgent.indexOf('Firefox') > -1 ) {
  1749. $body.find('> table').find('> thead').find('th[colspan="2"]').attr('colspan',1);
  1750. $list_items = $body.find('> table').find('> tbody').html();
  1751. $body.find('> table').remove();
  1752.  
  1753. $list_items = $list_items
  1754. .replace(/<table class="ellipsis"><tbody><tr><td>/g,'')
  1755. .replace(/<\/a><\/td><\/tr><\/tbody><\/table>/g,'')
  1756. .replace(/sortable-data="\d+?/g,'data-value="')
  1757. .replace(/<td data-value="(\d+)">([\d-]+)<\/td>\s*<td>(.+?)<\/td>/g,'<td data-value="$1">$2, $3</td>')
  1758. .replace(/<td(.+?)><a/g,'<td class="name" $1><a class="icon"');
  1759. }
  1760. // Default Chrome type
  1761. if ( $protocol === 'file:' && $body.find('> table').length > 0 && $userAgent.indexOf('Firefox') < 0) {
  1762. $list_items = $body.find('> table tbody').html();
  1763. $body.addClass('is_default_table').find('> table').remove();
  1764. }
  1765. $dir_list_body.append($list_items);
  1766. }
  1767. convertDirList();
  1768. // ***** END CONVERT INDEX PAGES ***** //
  1769.  
  1770. // Append directory list
  1771.  
  1772. var $dir_list_row = $dir_list_body.find('> tr');
  1773. var $dir_list_item_name = $dir_list_row.find('a').parent('td');
  1774. var $dir_list_details = $dir_list_item_name.nextAll();
  1775. $dir_list_details.addClass('details');
  1776.  
  1777. // ***** END CONVERT INDEX PAGES ***** //
  1778.  
  1779. // ***** PREPARE DIR LIST ***** //
  1780.  
  1781. var $this_link;
  1782. var $this_text;
  1783. var $this_ext;
  1784.  
  1785. // Get dir_list item row
  1786. var thisRow = function(x) {
  1787. return $(x).closest('#dir_list > tbody > tr').length > 0 ? $(x).closest('#dir_list > tbody > tr') : $(x).closest('#dir_list > li');
  1788. };
  1789. // Get row link
  1790. var thisLink = function(x) {
  1791. return $(x).find('a').length > 0 ? $(x).find('a').attr('href') : $(x).attr('href');
  1792. };
  1793. // Get row name
  1794. var thisText = function(x) {
  1795. return $(x).find('a').length > 0 ? decodeURIComponent( $(x).find('a').text().toLocaleLowerCase() ) : decodeURIComponent( $(x).text().toLocaleLowerCase() );
  1796. };
  1797. // get row text
  1798. var thisExt = function(x) {
  1799. let $this_link = thisLink(x).toLowerCase();
  1800. return $this_link.endsWith('app/') ? 'app' : $this_link.endsWith('/') ? '/' : $this_link.toLowerCase().slice( $this_link.lastIndexOf('.') + 1 );
  1801. };
  1802. // normalize typed strings for navigation
  1803. function stringNormalize(str) {
  1804. var diacritics = [
  1805. {char:'A', base:/[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g}, {char:'AA',base:/[\uA732]/g}, {char:'AE',base:/[\u00C6\u01FC\u01E2]/g}, {char:'AO',base:/[\uA734]/g}, {char:'AU',base:/[\uA736]/g}, {char:'AV',base:/[\uA738\uA73A]/g}, {char:'AY',base:/[\uA73C]/g},
  1806. {char:'B', base:/[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g},
  1807. {char:'C', base:/[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g},
  1808. {char:'D', base:/[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g}, {char:'DZ',base:/[\u01F1\u01C4]/g}, {char:'Dz',base:/[\u01F2\u01C5]/g},
  1809. {char:'E', base:/[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g},
  1810. {char:'F', base:/[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g},
  1811. {char:'G', base:/[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g},
  1812. {char:'H', base:/[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g},
  1813. {char:'I', base:/[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g},
  1814. {char:'J', base:/[\u004A\u24BF\uFF2A\u0134\u0248]/g},
  1815. {char:'K', base:/[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g},
  1816. {char:'L', base:/[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g}, {char:'LJ',base:/[\u01C7]/g}, {char:'Lj',base:/[\u01C8]/g},
  1817. {char:'M', base:/[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g},
  1818. {char:'N', base:/[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g}, {char:'NJ',base:/[\u01CA]/g}, {char:'Nj',base:/[\u01CB]/g},
  1819. {char:'O', base:/[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g}, {char:'OI',base:/[\u01A2]/g}, {char:'OO',base:/[\uA74E]/g}, {char:'OU',base:/[\u0222]/g},
  1820. {char:'P', base:/[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g},
  1821. {char:'Q', base:/[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g},
  1822. {char:'R', base:/[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g},
  1823. {char:'S', base:/[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g},
  1824. {char:'T', base:/[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g}, {char:'TZ',base:/[\uA728]/g},
  1825. {char:'U', base:/[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g},
  1826. {char:'V', base:/[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g}, {char:'VY',base:/[\uA760]/g},
  1827. {char:'W', base:/[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g},
  1828. {char:'X', base:/[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g},
  1829. {char:'Y', base:/[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g},
  1830. {char:'Z', base:/[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g},
  1831.  
  1832. {char:'a', base:/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g}, {char:'aa',base:/[\uA733]/g}, {char:'ae',base:/[\u00E6\u01FD\u01E3]/g}, {char:'ao',base:/[\uA735]/g}, {char:'au',base:/[\uA737]/g}, {char:'av',base:/[\uA739\uA73B]/g}, {char:'ay',base:/[\uA73D]/g},
  1833. {char:'b', base:/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g},
  1834. {char:'c', base:/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g},
  1835. {char:'d', base:/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g}, {char:'dz',base:/[\u01F3\u01C6]/g},
  1836. {char:'e', base:/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g},
  1837. {char:'f', base:/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g},
  1838. {char:'g', base:/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g},
  1839. {char:'h', base:/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g}, {char:'hv',base:/[\u0195]/g},
  1840. {char:'i', base:/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g},
  1841. {char:'j', base:/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g},
  1842. {char:'k', base:/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g},
  1843. {char:'l', base:/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g}, {char:'lj',base:/[\u01C9]/g},
  1844. {char:'m', base:/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g},
  1845. {char:'n', base:/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g}, {char:'nj',base:/[\u01CC]/g},
  1846. {char:'o', base:/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g}, {char:'oi',base:/[\u01A3]/g}, {char:'ou',base:/[\u0223]/g}, {char:'oo',base:/[\uA74F]/g},
  1847. {char:'p', base:/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g},
  1848. {char:'q', base:/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g},
  1849. {char:'r', base:/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g},
  1850. {char:'s', base:/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g},
  1851. {char:'t', base:/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g}, {char:'tz',base:/[\uA729]/g},
  1852. {char:'u', base:/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g},
  1853. {char:'v', base:/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g}, {char:'vy',base:/[\uA761]/g},
  1854. {char:'w', base:/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g},
  1855. {char:'x', base:/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g},
  1856. {char:'y', base:/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g},
  1857. {char:'z', base:/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g}
  1858. ];
  1859. diacritics.forEach(function(letter) {
  1860. str = str.replace(letter.base, letter.char);
  1861. });
  1862. return str;
  1863. }
  1864.  
  1865. // Dir_List: Classify items
  1866. function classifyDirListItems() {
  1867. $dir_list_row.each(function() {
  1868. let row = {};
  1869. $this_link = thisLink(this);
  1870. $this_text = thisText(this);
  1871. $this_ext = thisExt(this);
  1872. // add classes and data to row cells
  1873. $(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
  1874. // add dir or file classes
  1875. if ( $this_ext === '/' ) { // add tr data-ext
  1876. $(this).addClass('dir').attr('data-ext','dir');
  1877. } else {
  1878. $(this).addClass('file').attr('data-ext',$this_ext);
  1879. }
  1880. // add tr invisible class
  1881. if ( $this_text.startsWith('.') ) {
  1882. $(this).addClass('invisible');
  1883. }
  1884. // add kind and ext detail columns
  1885. $(this).append('<td class="detailsColumn details kind" data-value="File">File</td>')
  1886. .append('<td class="detailsColumn details ext" data-value="'+ $this_ext +'">&nbsp;</td>');
  1887. // Classify "other" if extension is not in $row_types.
  1888. if ( !JSON.stringify($row_types).includes($this_ext) ) {
  1889. $(this).addClass('other').attr('data-type','Other').attr('data-kind','other').find('td.kind').attr('data-value','other').text('Other');
  1890. }
  1891. for ( let key in $row_types ) { // Categorize rows by listed supported file types
  1892. if ( $row_types[key].includes($this_ext) ) {
  1893. let $display_key = key.charAt(0).toUpperCase() + key.slice(1); // make display name for file type
  1894. let kind = $display_key === undefined ? 'File' : $display_key;
  1895. $(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
  1896.  
  1897. if ( key === 'audio' || key === 'video' ) { // media classes and prepend checkbox
  1898. $(this).addClass('media').find('a').prepend('<input type="checkbox" tabindex="-1" checked="true" />');
  1899. };
  1900. }
  1901. }
  1902. // For ignored and excluded items
  1903. for ( let key in $row_settings ) {
  1904. if ( $row_settings[key].includes($this_ext) ) {
  1905. $(this).addClass(key);
  1906. }
  1907. if ( $row_settings[key].includes($this_ext) && key == 'ignore' ) {
  1908. }
  1909. }
  1910.  
  1911. }); // end classify dir_list items
  1912. }
  1913. classifyDirListItems();
  1914.  
  1915. // ***** END DIR_LIST SETUP ***** //
  1916.  
  1917. // ***** UI SETUP ***** //
  1918.  
  1919. // Add body classes
  1920. function setupUI() {
  1921. if ( $dir_list.find('.audio').length > 0 ) {
  1922. $body.add($dir_list).addClass('has_audio has_media');
  1923. }
  1924. if ( $dir_list.find('.font').length > 0 ) {
  1925. $body.addClass('has_fonts');
  1926. }
  1927. if ( $dir_list.find('.image').length > 0 ) {
  1928. $body.addClass('has_images');
  1929. }
  1930. if ( $dir_list.find('.video').length > 0 ) {
  1931. $body.add($dir_list).addClass('has_video has_media');
  1932. }
  1933. setQuery('autoload_media',$UI_pref_autoload_media);
  1934. if ( $UI_pref_autoload_media === true ) {
  1935. $body.addClass('autoload_media');
  1936. $autoload_media.addClass('checked');
  1937. } else {
  1938. $body.removeClass('autoload_media');
  1939. $autoload_media.removeClass('checked');
  1940. }
  1941. if ( $settings.autoload_index_files === true ) {
  1942. $body.addClass('autoload_index_files');
  1943. }
  1944. if ( $settings.use_custom_icons === true ) {
  1945. $body.addClass('use_custom_icons')
  1946. } else {
  1947. $body.addClass('use_default_icons');
  1948. }
  1949. if ( $settings.hide_ignored_files === true ) {
  1950. $body.addClass('hide_ignored');
  1951. }
  1952. if ( navigator.platform.indexOf('Win') > -1 || $location.indexOf('file:') < 0 ) {
  1953. $inv_checkbox.hide();
  1954. } else if ( $settings.hide_invisibles === true ) {
  1955. $inv_checkbox.find('input').prop('checked',true);
  1956. $body.addClass('hide_invisibles').removeClass('show_invisibles')
  1957. } else {
  1958. $inv_checkbox.find('input').prop('checked',false);
  1959. $body.addClass('show_invisibles').removeClass('hide_invisibles')
  1960. }
  1961. setQuery('alternate_background',$UI_pref_background);
  1962. if ( $UI_pref_background === true ) {
  1963. $body.addClass('alternate_background');
  1964. $toggle_alt_bckgrd.addClass('checked');
  1965. } else {
  1966. $body.removeClass('alternate_background');
  1967. $toggle_alt_bckgrd.removeClass('checked');
  1968. }
  1969. if ( $UI_pref_details === false ) {
  1970. $dir_list.addClass('show_details');
  1971. $dir_list_body.css({'top':$dir_list_head.height() + 1 +'px'});
  1972. $details_btn.find('span').first().hide().next().show();
  1973. }
  1974. if ( $UI_pref_theme === true ) {
  1975. $body.addClass('dark_theme');
  1976. } else {
  1977. $body.addClass('light_theme');
  1978. }
  1979. if ( $userAgent.indexOf('Chrome') > 0 ) { $body.addClass('is_chrome'); }
  1980. if ( $userAgent.indexOf('Firefox') > 0 ) { $body.addClass('is_gecko'); }
  1981. };
  1982. setupUI();
  1983.  
  1984. // Build UI
  1985. function buildUI() {
  1986. $dir_list.appendTo($sidebar);
  1987.  
  1988. var $main_content = $('<table id="main_content"><tbody><tr></tr></tbody></table>');
  1989. $main_content.find('tr').append( $sidebar_wrapper, $content_pane );
  1990.  
  1991. $body.empty().prepend($main_content);
  1992.  
  1993. if ( $body.hasClass('is_converted_list') ) {
  1994. $('#sort_by_size, #sort_by_date, #size, #date').addClass('disabled');
  1995. }
  1996. if ( $body.hasClass('is_converted_table') || $body.hasClass('is_converted_pre') ) {
  1997. if ( $dir_list.find('td.size').length < 1 ) {
  1998. $('#sort_by_size,th#size').addClass('disabled');
  1999. }
  2000. if ( $dir_list.find('td.date').length < 1 ) {
  2001. $('#sort_by_date,th#date').addClass('disabled');
  2002. }
  2003. }
  2004. }
  2005. buildUI();
  2006.  
  2007. // ***** BASIC UI FUNCTIONS ***** //
  2008. // Show Menus
  2009. function showMenus(el) {
  2010. var $position = $(el).position();
  2011. $(el).find('> ul').css({'top':$position.top + $(el).innerHeight() + 'px'}).toggle().parent('td').siblings('td').find('.menu').hide();
  2012. }
  2013. $parents_dir_menu.add($shortcuts_menu).parent('td').on('click',function(e) {
  2014. e.stopPropagation();
  2015. showMenus(this);
  2016. });
  2017. // Hide Menus
  2018. function hideMenus() {
  2019. $('.menu').hide();
  2020. }
  2021. $(document).on('click', hideMenus );
  2022. // Toggle Invisibles
  2023. function toggleInvisibles() {
  2024. $body.toggleClass('hide_invisibles').toggleClass('show_invisibles');
  2025. }
  2026. $inv_checkbox.on('click','input', toggleInvisibles );
  2027. // Toggle Autoload Media
  2028. function toggleAutoloadMedia(e) {
  2029. e.preventDefault();
  2030. $body.toggleClass('autoload_media');
  2031. toggleQuery('autoload_media');
  2032. $autoload_media.toggleClass('checked');
  2033. }
  2034. $autoload_media.on('click', toggleAutoloadMedia );
  2035. // Toggle Dark Theme
  2036. function toggleDarkMode(e) {
  2037. e.preventDefault();
  2038. $body.toggleClass('dark_theme').toggleClass('light_theme');
  2039. toggleQuery('dark_theme');
  2040. }
  2041. $toggle_dark_theme.on('click', toggleDarkMode );
  2042. // Toggle Alternating Row Background
  2043. function toggleAlternateBackground(e) {
  2044. e.preventDefault();
  2045. $body.toggleClass('alternate_background');
  2046. toggleQuery('alternate_background');
  2047. $toggle_alt_bckgrd.toggleClass('checked');
  2048. }
  2049. $toggle_alt_bckgrd.on('click', toggleAlternateBackground );
  2050. // Toggle Details
  2051. function toggleDetails() {
  2052. $dir_list.toggleClass('show_details');
  2053. $dir_list_body.css({'top':$dir_list_head.height() + 1 +'px'});
  2054. $details_btn.find('span').toggle();
  2055. toggleQuery('hide_details');
  2056. }
  2057. $details_btn.on('click', toggleDetails );
  2058. // Show/Hide Sidebar
  2059. function toggleSidebar() {
  2060. $body.toggleClass('has_hidden_sidebar');
  2061. setContentHeight();
  2062. }
  2063. $toggle_sidebar.on('click', function() {
  2064. toggleSidebar();
  2065. scrollThis('dir_list','selected',true); // true = instant scroll
  2066. });
  2067. // Default settings: remove queries;
  2068. function defaultSettings() {
  2069. $location = window.location.href;
  2070. $location = $location.slice(0,$location.lastIndexOf('?'));
  2071. window.location.assign($location);
  2072. }
  2073. $default_settings.on('click', function() {
  2074. if (window.confirm( "Are you sure you want to reset all your temporary UI settings to the defaults in your user_settings?" ) ) {
  2075. defaultSettings();
  2076. }
  2077. });
  2078.  
  2079. //***** UI SETUP *****//
  2080.  
  2081. // Set Content Height
  2082. function setContentHeight() {
  2083. let $window_height = window.innerHeight;
  2084. let $dir_list_head_height = $dir_list_head.length ? $dir_list_head.height() : 0;
  2085. let $content_header_height = $content_header.outerHeight();
  2086. $dir_list.add($dir_list_wrapper).css({'height':$window_height - $sidebar_header.outerHeight() });
  2087. $dir_list.find($dir_list_body).css({'top': $dir_list_head_height });
  2088. $content_image.add($prev_btn).add($next_btn).css({'top':$content_header_height });
  2089. $content_grid.add($content_pdf).add($content_iframe).add($content_font).css({'height':$window_height - $content_header_height,'top':$content_header_height });
  2090. $content_scale.css({'top':$content_header_height + 13 });
  2091. $('#prev_track, #next_track, #close_audio').css({'height':$audio.height() });
  2092. }
  2093. window.addEventListener('resize', function(e) {
  2094. setContentHeight();
  2095. });
  2096.  
  2097. // Set Content Title
  2098. const $title = $content_pane.find('#title');
  2099.  
  2100. function setContentTitle() {
  2101. let $title_text = '';
  2102. if ( $('.playing').hasClass('audio') ) { // set audio player title
  2103. $content_audio_title.find('td').empty().append( $('.playing').find('td.name a').text() );
  2104. }
  2105. if ( $content_pane.hasClass('has_grid') ) { // set main title for other content
  2106. $title_text = $current_dir_name;
  2107. } else {
  2108. $title_text = $title_text + $('.selected:not(.audio)').find('td.name a').text(); // Assemble title
  2109. }
  2110. $title.empty().text( $title_text ); // Add file name
  2111. if ( $('.selected').hasClass('image') && !$content_pane.hasClass('has_grid') ) {
  2112. setDimensions($('.selected.image'));
  2113. }
  2114. }
  2115. // Get Image Dimensions
  2116. function getDimensions(link, callback) {
  2117. var img = new Image();
  2118. img.src = link;
  2119. img.onload = function() { callback( this.width, this.height ); };
  2120. }
  2121. function setDimensions(row) {
  2122. getDimensions( thisLink(row), function( width, height ) {
  2123. $title.append(' <span style="text-transform:lowercase;">(' + width + 'px &times; ' + height + 'px</span>)' );
  2124. });
  2125. }
  2126. // Scroll Selected Items
  2127. function scrollThis(elID,className,bool) {
  2128. let $behavior = 'smooth';
  2129. bool === undefined ? null : $behavior = 'instant';
  2130. var $block = $userAgent.indexOf('Firefox') > -1 ? $block = 'start' : $block = 'nearest';
  2131. if ( document.getElementsByClassName(className).length > 0 ) {
  2132. document.getElementById(elID).getElementsByClassName(className)[0].scrollIntoView({ behavior:$behavior, block:$block, inline:'nearest' });
  2133. }
  2134. }
  2135.  
  2136. // ***** SORTING ***** //
  2137. var $sort_all = $dir_list_body.find('tr');
  2138. var $sort_dirs = $dir_list_body.find('tr.dir').not('.app');
  2139. var $sort_files = $dir_list_body.find('tr').not('.dir').add('.app');
  2140. var currentIndex, prevIndex;
  2141. var $default_index = $('#thead').find('#default').index();
  2142. var $sort_order = 1;
  2143.  
  2144. var dataName = function(x) {
  2145. return $(x).find('td').eq(0).data('value').toString();
  2146. };
  2147. var dataData = function(x,index) {
  2148. return $(x).find('td').eq(index).attr('data-value') === undefined ? '' : $(x).find('td').eq(index).attr('data-value').toString();
  2149. };
  2150. // Sort comparison functions
  2151. var sortByName = function(x,y,sortDirection) {
  2152. return sortDirection === 1 ? dataName(x).localeCompare(dataName(y)) : dataName(y).localeCompare(dataName(x));
  2153. };
  2154. var sortByData = function(x,y,index,sortDirection) { // sort by data then by name
  2155. if ( sortDirection === 1 ) {
  2156. 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 });
  2157. } else {
  2158. 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 });
  2159. }
  2160. };
  2161. // Sort elements: sort by name if index = 0, else sort by data-value.
  2162. var sortEls = function(els,index,sortDirection) {
  2163. els.detach().sort(function(a,b) {
  2164. return index === 0 ? sortByName(a,b,sortDirection) : sortByData(a,b,index,sortDirection);
  2165. });
  2166. };
  2167. // Sort Dir List
  2168. function sortDirList(index, sortDirection) {
  2169. $dir_list_row.removeClass('sorted');
  2170.  
  2171. if ( index == $default_index ) {
  2172. if ( index == $default_index ) {
  2173. index = 0;
  2174. }
  2175. // sort dirs and files separately
  2176. sortEls( $sort_dirs, index, sortDirection );
  2177. sortEls( $sort_files, index, sortDirection );
  2178. if (sortDirection === 1 ) { // append dirs and files according to sort direction
  2179. $.each($sort_dirs, function() { $dir_list_body.append(this); });
  2180. $.each($sort_files, function() { $dir_list_body.append(this); });
  2181. } else {
  2182. $.each($sort_files, function() { $dir_list_body.append(this); });
  2183. $.each($sort_dirs, function() { $dir_list_body.append(this); });
  2184. }
  2185. } else if ( $settings.dirs_on_top === false || index == 0 && index !== $default_index ) { // sort all dirs and files, append, remove body class
  2186. sortEls( $sort_all, index, sortDirection );
  2187. $.each($sort_all, function() { $dir_list_body.append(this); });
  2188. $body.removeClass('is_dirs_on_top');
  2189. } else if ( $settings.dirs_on_top === true || index !== 0 ) { // sort dirs and files separately
  2190. sortEls( $sort_dirs, index, sortDirection );
  2191. sortEls( $sort_files, index, sortDirection );
  2192. if (sortDirection === 1 ) { // append dirs and files according to sort direction
  2193. $.each($sort_dirs, function() { $dir_list_body.append(this); });
  2194. $.each($sort_files, function() { $dir_list_body.append(this); });
  2195. } else {
  2196. $.each($sort_files, function() { $dir_list_body.append(this); });
  2197. $.each($sort_dirs, function() { $dir_list_body.append(this); });
  2198. }
  2199. }
  2200. if ( index === $('#thead').find('#kind').index() || index === $('#thead').find('#ext').index() ) {
  2201. $body.addClass('is_dirs_on_top');
  2202. addSortStyle( index, sortDirection );
  2203. }
  2204. }
  2205. // Add Sort Styles (for rules)
  2206. function addSortStyle(index, sortDirection) {
  2207. var $this_value, $next_value;
  2208. $dir_list_row.removeClass('sorted');
  2209.  
  2210. $dir_list_row.filter(':visible').not('.dir').add('.app').each(function() {
  2211. $this_value = $(this).find('td').eq(index).attr('data-value');
  2212. $next_value = $(this).next('tr:visible').find('td').eq(index).attr('data-value');
  2213.  
  2214. if ( $this_value !== $next_value ) {
  2215. $(this).addClass('sorted');
  2216. }
  2217. if ( $(this).attr('data-type') !== $(this).next('tr:visible').attr('data-type') ) {
  2218. $(this).addClass('sorted');
  2219. }
  2220. });
  2221. // for last dir
  2222. if ( sortDirection == 1 ) {
  2223. $dir_list_row.filter('.dir').last().addClass('sorted');
  2224. }
  2225. }
  2226. // Sort on click
  2227. function clickSort(x,y) { // x = clicked sort link, y = bool (false ? don't set query_prefs)
  2228. currentIndex = x.index();
  2229. // Only reverse sort order after clicking a sort column once.
  2230. if ( currentIndex != prevIndex ) {
  2231. prevIndex = currentIndex;
  2232. $sort_order = 1;
  2233. }
  2234.  
  2235. sortDirList(currentIndex, $sort_order);
  2236. $sort_order = -1 * $sort_order;
  2237.  
  2238. x.toggleClass('up').addClass('checked').siblings().removeClass('checked up');
  2239. $('#sort_menu').find('li').eq(currentIndex).addClass('checked').siblings().removeClass('checked');
  2240.  
  2241. setContentHeight();
  2242. // set sorting query pref
  2243. if ( y === true ) {
  2244. $query_prefs = getQueryPrefs();
  2245. $query_prefs.set('sort',$('#sort_menu').find('li').eq(currentIndex).text().toLowerCase() );
  2246. updateQuery();
  2247. }
  2248. }
  2249. // Sort on clicking dir_list sort header
  2250. $('#thead').on('click','th:not(.disabled)',function(e) {
  2251. e.preventDefault();
  2252. e.stopPropagation();
  2253. var i = $(this).index();
  2254. clickSort( $(this),true)
  2255. });
  2256. // Sort Menu: click the list header that contains the selected menu text.
  2257. $('#sort_menu').on('click','li:not(.disabled)',function(e) {
  2258. e.preventDefault();
  2259. e.stopPropagation();
  2260. $(this).addClass('checked').siblings().removeClass('checked');
  2261. $('#theader th[id*="'+ thisText($(this)).slice(0,2) +'" i]').click();
  2262. });
  2263. // Initialize sort
  2264. function initialSort() {
  2265. switch ( $UI_pref_sort ) {
  2266. case 'name':
  2267. i = 0;
  2268. break;
  2269. case 'size':
  2270. i = 1;
  2271. break;
  2272. case 'date':
  2273. i = 2;
  2274. break;
  2275. case 'kind':
  2276. i = 3;
  2277. break;
  2278. case 'extension':
  2279. i = 4;
  2280. break;
  2281. default:
  2282. i = 5;
  2283. }
  2284. $('#theader').find('th').eq(i).click();
  2285. }
  2286. initialSort();
  2287.  
  2288. // ***** END SORTING ***** //
  2289. // ***** END BASIC UI FUNCTIONS ***** //
  2290.  
  2291. // ***** CONTENT PREVIEW ***** //
  2292.  
  2293. var $selected = $dir_list.find('.selected');
  2294. var $playing = $dir_list.find('.playing');
  2295. var $media = $dir_list.find('td').filter('.media:not(.unchecked):not(.ignored)');
  2296.  
  2297. // SELECT ROW on click and set classes for $content_pane
  2298. function selectThis(row) {
  2299. 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();
  2300. if ( $content_pane.hasClass('has_grid') ) { // Select corresponding grid item
  2301. row.addClass('selected').siblings().removeClass('selected hovered');
  2302. $grid_selected.addClass('selected').siblings().removeClass('selected');
  2303. } else if ( row.hasClass('audio') || row.hasClass('video') ) { // if row is audio or video, remove playing and selected classes
  2304. row.addClass('selected playing').siblings().removeClass('selected hovered').filter('.media').removeClass('playing');
  2305. } else { // remove classes from rows, but leave .audio with playing
  2306. row.addClass('selected').siblings().removeClass('selected hovered').siblings('.video').removeClass('playing');
  2307. }
  2308. }
  2309.  
  2310. //***** SHOW CONTENT *****//
  2311. // Show Font
  2312. var $font_family_arr = [];
  2313. function showFont(row) {
  2314. var $font_family = thisText(row);
  2315. if ( $font_family_arr.indexOf($font_family) == -1 ) { // if font family is not in array, add it
  2316. $font_family_arr.push($font_family);
  2317. $font_preview_styles.append('@font-face { font-family: "'+ $font_family +'"; src: url("'+ thisLink(row) +'"); }'); // only add style if it doesn't exist
  2318. }
  2319. $content_font.css({ 'font-family':'"'+ $font_family +'"' });
  2320. }
  2321. // Show Grid
  2322. function showGrid() {
  2323. $content_pane.removeClass('has_hidden_grid').addClass('has_grid');
  2324. setContentSource($content_grid);
  2325. setContentTitle();
  2326. setContentHeight();
  2327. selectThis($('.selected'));
  2328. }
  2329. // Hide Grid
  2330. function hideGrid() {
  2331. if ( $content_pane.hasClass('has_grid') ) {
  2332. $content_pane.removeClass('has_grid').addClass('has_hidden_grid');
  2333. }
  2334. }
  2335. // Set Content Source
  2336. function setContentSource(el,link) { // add source attribute to content containers, excluding $content_font
  2337. let $el_ID = $(el).attr('id');
  2338. switch ($el_ID) {
  2339. case 'content_audio':
  2340. $(el).find('audio').attr('src',link);
  2341. break;
  2342. case 'content_image':
  2343. $(el).find('img').attr('src',link);
  2344. break;
  2345. case 'content_font':
  2346. break;
  2347. default:
  2348. $(el).attr('src',link);
  2349. }
  2350. }
  2351. // SHOW CONTENT
  2352. function showContent(row) { // show any content excluding grids
  2353. if ( row.length > 0 ) { // needed for left/right arrow nav when there are no valid items (i.e. images or fonts)
  2354. let kind = thisRow(row).hasClass('ignore') ? 'ignored' : thisRow(row).attr('data-kind');
  2355. !['audio','font','image','pdf','video','ignored'].includes(kind) || kind === 'dir' ? kind = 'iframe' : null; // load dirs and any other kind of content into iframe
  2356. let link = kind === 'pdf' ? thisLink(row) + '?#view=fitB&scrollbar=1&toolbar=1&navpanes=1' : thisLink(row); // add pdf query str to link
  2357. $content_pane.hasClass('has_grid') ? hideGrid() : null;
  2358. closeAllContent(); // except audio and grids
  2359. if ( kind === 'ignored' ) {
  2360. $content_pane.addClass('has_ignored');
  2361. } else {
  2362. setContentSource( '#content_'+ kind, link ); // set content container source
  2363. kind === 'font' ? showFont(row) : null; // sets $content_font css
  2364. kind === 'image' ? $content_grid.find('a[href="' + thisLink(row) + '"]').parent('div').addClass('selected').siblings().removeClass('selected') : null;
  2365. kind === 'video' ? closeAudio() : null;
  2366. }
  2367. thisRow(row).attr('data-kind') === 'dir' ? $content_pane.addClass( 'has_dir' ) : $content_pane.addClass( 'has_'+ kind ); // add content pane class
  2368. setContentHeight();
  2369. setContentTitle();
  2370. kind === 'audio' ? autoLoadCoverArt() : null; // autoload cover art on selecting audio
  2371. }
  2372. }
  2373.  
  2374. //***** CLOSE CONTENT (Close button or Cmd/Ctrl + W) *****//
  2375. // Close Audio
  2376. function closeAudio() {
  2377. $('.audio.playing').removeClass('playing');
  2378. $content_pane.removeClass('has_audio').find('#content_audio_title td').empty();
  2379. $audio.trigger('pause').attr('src','');
  2380. }
  2381. // Close audio button click
  2382. $close_audio.on('click',function(e) {
  2383. e.stopPropagation();
  2384. closeAudio();
  2385. });
  2386. // Close Grid
  2387. function closeGrid() {
  2388. $content_pane.removeClass('has_grid');
  2389. $content_grid.empty().removeClass().attr('style','').find('.image_grid_item, img').attr('style','');
  2390. }
  2391. // Close content excluding audio and grids
  2392. function closeAllContent() {
  2393. let $content_classes = $content_pane.attr('class') !== undefined ? $content_pane.attr('class').split(' ') : []; // get array of content pane classes
  2394. for ( let $content_class of $content_classes ) { // for each content_pane class, define the associated content container, remove class and source
  2395. let $container_name = '#content_'+ $content_class.substring(4);
  2396. let $content_el = $( $container_name ); // define content container from content pane class name
  2397. 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
  2398. $content_pane.removeClass($content_class);
  2399. if ( $content_class === 'has_font' ) { // remove font style
  2400. $content_font.css({'font-family':''})
  2401. $content_font.css({'font-size':'1em'}); // reset font: comment out to retain font scale after loading other content
  2402. } else if ( $content_class === 'has_image' ) { // remove image src
  2403. $content_image.find('img').attr('src','');
  2404. $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
  2405. } else if ( $content_class === 'has_video' ) { // remove video src
  2406. $content_video.trigger('pause').attr('src','');
  2407. } else {
  2408. $content_el.attr('src',''); // remove src for anything else
  2409. $content_iframe.attr('src',''); // remove src for anything else
  2410. }
  2411. }
  2412. }
  2413. $content_title.removeClass().find('#title').empty();
  2414. setContentHeight();
  2415. }
  2416. // CLOSE CONTENT
  2417. function closeContent() {
  2418. if ( $content_pane.hasClass('has_grid') ) {
  2419. closeGrid(); // if has_grid, close grid
  2420. } else if ( $content_pane.hasClass('has_hidden_grid') ) {
  2421. showGrid(); // if has_hidden_grid, close content, show grid
  2422. } 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 ) ) {
  2423. closeAllContent(); // close content if content pane has one of these classes, except audio and grids
  2424. } else { // close audio last of all
  2425. closeAudio();
  2426. }
  2427. setContentHeight();
  2428. }
  2429. $content_close_btn.on('click', function(e) {
  2430. e.preventDefault();
  2431. e.stopPropagation();
  2432. closeContent();
  2433. });
  2434.  
  2435. //***** RESET CONTENT (Reset button or Cmd/Ctrl + R) *****//
  2436. function resetContent() {
  2437. if ( $content_pane.attr('class') === '' ) { window.location = window.location.href } // reload page
  2438. if ( $content_pane.hasClass('has_audio') ) { $audio.prop('currentTime', 0).trigger('pause'); }
  2439. if ( $content_pane.hasClass('has_grid') ) { $grid_btn.click(); }
  2440. if ( $content_pane.hasClass('has_font') ) { $content_font.css({'font-size':'1em'});}
  2441. if ( $content_pane.hasClass('has_image') ) { $content_image.attr('data-image-scale-factor',1).find('img').removeClass('zoom_img').attr('style',''); }
  2442. if ( $content_pane.hasClass('has_video') ) { $content_video.prop('currentTime',0).trigger('pause'); }
  2443. if ( $content_pane.hasClass('has_iframe') || $content_pane.hasClass('has_dir') ) { $('.selected').find('a').click(); }
  2444. setContentHeight();
  2445. }
  2446. $('#reload_btn').on('click', function(e) {
  2447. e.preventDefault();
  2448. e.stopPropagation();
  2449. resetContent();
  2450. });
  2451.  
  2452. //***** SELECT ROWS *****//
  2453. var prevRow = function(className) {
  2454. 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();
  2455. };
  2456. 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
  2457. 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();
  2458. };
  2459.  
  2460. //***** SELECT ROWS by typed string *****//
  2461. var str = '', timer;
  2462. function timeoutID() {
  2463. timer = window.setTimeout( function() {
  2464. str = '';
  2465. }, 1500 );
  2466. }
  2467. function alphaNav(e) { // Select Dir_List row by typed string; Todo: select next row by nearest letter
  2468. if (typeof timer == 'number' ) {
  2469. window.clearTimeout( timer );
  2470. timer = 0; // id
  2471. }
  2472. timeoutID();
  2473. str += e.key
  2474. $('#dir_list').find('td.name[data-value^="'+ str +'"]').first().find('a').click();
  2475. }
  2476.  
  2477. // Click Prev/Next Arrows
  2478. function prevNextBtn(el) {
  2479. let f = $.Event("keydown");
  2480. if ( el.attr('id') === 'prev_btn' ) {
  2481. f.key = 'ArrowLeft';
  2482. }
  2483. if ( el.attr('id') === 'next_btn' ) {
  2484. f.key = 'ArrowRight';
  2485. }
  2486. $body.trigger(f);
  2487. }
  2488. $content_pane.on( 'click','#prev_btn, #next_btn', function(e) {
  2489. e.stopPropagation();
  2490. e.preventDefault();
  2491. prevNextBtn( $(this) );
  2492. });
  2493. // Click grid button
  2494. $sidebar_header.on('click', '#grid_btn, #show_font_grid, #show_image_grid', function(e) {
  2495. makeGrids(e,$(this));
  2496. showGrid();
  2497. });
  2498.  
  2499. // SELECT ROWS prev/next media track with arrow keys
  2500. function playPrevNextTrack(key) {
  2501. let $key = key.key;
  2502. $shuffle.find('input').prop('checked') === true ? shuffle() : null;
  2503. $playing = $('.playing');
  2504. let kind = $playing.attr('data-kind');
  2505. if ( $body.hasClass('has_media') ) {
  2506. if ( $playing.length === 0 ) { // if no media selected, select first shuffle or first media file
  2507. if ( $shuffle.find('input').prop('checked') === true ) {
  2508. clickRow( $('.audio').eq($count) );
  2509. } else {
  2510. switch ( $key ) {
  2511. case 'ArrowLeft': clickRow( $media.last() ); break;
  2512. case 'ArrowRight': clickRow( $media.first() ); break;
  2513. }
  2514. }
  2515. }
  2516. if ( $playing.length == 1 ) {
  2517. if ( $shuffle.find('input').prop('checked') === true ) {
  2518. selectThis( $('.audio').eq($count) );
  2519. showContent( $('.audio').eq($count) );
  2520. $shuffleList.length !== 0 ? playMedia('play') : null;
  2521. } else {
  2522. switch ( key ) {
  2523. case 'ArrowLeft':
  2524. if ( $playing.prevAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').length === 0 ) {
  2525. clickRow( $dir_list_row.filter('.media[data-kind="'+ kind +'"]:not(.unchecked)').last() );
  2526. } else {
  2527. clickRow( $playing.prevAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').first() );
  2528. }
  2529. break;
  2530. case 'ArrowRight':
  2531. if ( $playing.nextAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').length === 0 && $loop.find('input').is(':checked') === false ) { // loop playback
  2532. selectThis( $dir_list_row.filter('.media[data-kind="'+ kind +'"]:not(.unchecked)').first() );
  2533. autoLoadCoverArt();
  2534. return true;
  2535. } else if ( $playing.nextAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').length === 0 ) { // else click first media file
  2536. clickRow( $dir_list_row.filter('.media[data-kind="'+ kind +'"]:not(.unchecked)').first() );
  2537. } else {
  2538. clickRow( $playing.nextAll('.media[data-kind="'+ kind +'"]:not(.unchecked)').first() ); // else click next media file
  2539. }
  2540. break;
  2541. }
  2542. }
  2543. playMedia('play');
  2544. }
  2545. }
  2546. }
  2547.  
  2548. // SELECT ROWS by arrow keys
  2549. function leftRightArrowNavigation(key,kind) {
  2550. if ( $body.hasClass('has_media') && $('.selected').hasClass('audio') && !$('.selected').hasClass('playing') ) { // play selected track (e.g., after up/down arrow)
  2551. clickRow( $('.audio.selected') );
  2552. playMedia('play');
  2553. } else if ( $body.hasClass('has_media') && $('.selected').hasClass('audio')) { // play next audio track
  2554. playPrevNextTrack(key);
  2555. playMedia('play');
  2556. } else { // select prev/next row of same sort kind
  2557. if ( key === 'ArrowLeft' ) { clickRow( prevRow('[data-kind="'+ kind +'"]:visible') ); }
  2558. if ( key === 'ArrowRight' ) { clickRow( nextRow('[data-kind="'+ kind +'"]:visible') ); }
  2559. if ( $('.selected').hasClass('video') ) { playMedia('play') };
  2560. }
  2561. }
  2562. function arrowNavigation(e) {
  2563. let $key = e.key;
  2564. let $selected = $('.selected');
  2565. let kind = $('.selected').attr('data-kind') === undefined ? $dir_list.find('tbody tr:visible').first().attr('data-kind') : $('.selected').attr('data-kind');
  2566. if ( $content_pane.hasClass('has_grid') ) { // Grid navigation: L/R arrow selects images and fonts only; U/D arrow clicks row.
  2567. switch ( $key ) {
  2568. case 'ArrowUp': clickRow( prevRow('.dir:visible,.file:visible') ); break;
  2569. case 'ArrowDown': clickRow( nextRow('.dir:visible,.file:visible') ); break;
  2570. case 'ArrowLeft':
  2571. if ( $content_grid.hasClass('has_font_grid') ) {
  2572. selectThis( prevRow('.font:visible') );
  2573. } else if ( $content_grid.hasClass('has_image_grid') ) {
  2574. selectThis( prevRow('.image:not(.ignore):visible') );
  2575. } else {
  2576. selectThis( prevRow('.image:not(.ignore):visible,.font:visible') );
  2577. }
  2578. break;
  2579. case 'ArrowRight':
  2580. if ( $content_grid.hasClass('has_font_grid') ) {
  2581. selectThis( nextRow('.font:visible') );
  2582. } else if ( $content_grid.hasClass('has_image_grid') ) {
  2583. selectThis( nextRow('.image:not(.ignore):visible') );
  2584. } else {
  2585. selectThis( nextRow('.image:not(.ignore):visible,.font:visible') );
  2586. }
  2587. break;
  2588. }
  2589. } else {
  2590. switch ( $key ) {
  2591. 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.
  2592. if ( prevRow('.dir:visible,.file:visible').hasClass('audio') ) {
  2593. closeAllContent(); // except audio and grids
  2594. if ( !$content_pane.hasClass('has_audio') ) {
  2595. $('.selected:not(.audio)').removeClass('selected');
  2596. clickRow( prevRow('.audio:visible') );
  2597. } else {
  2598. prevRow('.audio:visible').addClass('selected').siblings().removeClass('selected hovered');
  2599. autoLoadCoverArt();
  2600. }
  2601. } else {
  2602. clickRow( prevRow('.dir:visible,.file:visible') );
  2603. }
  2604. break;
  2605. case 'ArrowDown':
  2606. if ( nextRow('.dir:visible,.file:visible').hasClass('audio') ) { // if audio player closed, click row to load and show, else only select next audio row.
  2607. closeAllContent(); // except audio and grids
  2608. if ( !$content_pane.hasClass('has_audio') ) {
  2609. $('.selected:not(.audio)').removeClass('selected');
  2610. clickRow( nextRow('.audio:visible') );
  2611. } else {
  2612. nextRow('.audio:visible').addClass('selected').siblings().removeClass('selected hovered');
  2613. autoLoadCoverArt();
  2614. }
  2615. } else {
  2616. clickRow( nextRow('.dir:visible,.file:visible') );
  2617. }
  2618. break;
  2619. case 'ArrowLeft':
  2620. leftRightArrowNavigation('ArrowLeft',kind);
  2621. break;
  2622. case 'ArrowRight':
  2623. leftRightArrowNavigation('ArrowRight',kind);
  2624. break;
  2625. }
  2626. }
  2627. scrollThis('dir_list','selected');
  2628. $content_pane.hasClass('has_grid') && $content_grid.find('.selected').length > 0 ? scrollThis('content_grid','selected') : null;
  2629. }
  2630.  
  2631. //***** CLICK & HOVER EVENTS for Content *****//
  2632.  
  2633. // HOVER Grid Item
  2634. $content_grid.on('mouseenter','> div:not(.selected)',function() {
  2635. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).addClass('hovered');
  2636. }).on('mouseleave','> div:not(.selected)',function() {
  2637. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).removeClass('hovered');
  2638. });
  2639. // HOVER Dir_list_row and highlight corresponding grid item
  2640. $dir_list_row.hover(function() {
  2641. if ( $content_grid.is(':visible') ) {
  2642. $content_grid.find('[href*="'+ thisLink(this) +'"]').closest('div').addClass('hovered');
  2643. }
  2644. }, function() {
  2645. if ( $content_grid.is(':visible') ) {
  2646. $content_grid.find('.hovered').removeClass('hovered');
  2647. }
  2648. });
  2649. // CLICK grid item
  2650. function gridItemClick(e,el) {
  2651. e.preventDefault();
  2652. clickThis($dir_list.find('a[href*="'+ thisLink(el) +'"]') );
  2653. }
  2654. $content_grid.on('click','div', function(e) {
  2655. gridItemClick(e,$(this));
  2656. });
  2657.  
  2658. // CLICK Row
  2659. function clickRow(row) { // Pause/Play audio if .selected = audio, else click row normally
  2660. if ( thisRow(row).hasClass('playing') && !thisRow(row).hasClass('video') ) {
  2661. playPauseMedia();
  2662. } else {
  2663. selectThis( thisRow(row) );
  2664. showContent( thisRow(row) );
  2665. }
  2666. hideMenus();
  2667. }
  2668. // CLICK Row
  2669. $dir_list_row.on('click','a', function(e) {
  2670. e.preventDefault();
  2671. e.stopPropagation();
  2672. clickRow( $(this) );
  2673. });
  2674. // Click element anchor
  2675. function clickThis(el) {
  2676. el.find('a').length > 0 ? el.find('a').click() : el.click();
  2677. }
  2678. // DOUBLE-CLICK Row to open directory
  2679. function doubleClickRow(row) {
  2680. if ( row.hasClass('dir') ) {
  2681. $query_prefs = getQueryPrefs();
  2682. var $history = $query_prefs.get('history') === null ? '' : '+'+ $query_prefs.get('history');
  2683. var $current_index = row.prevAll('.dir:visible:not(.ignore)').length;
  2684. $history = $current_index + $history; // update history
  2685. $query_prefs.delete('selected');
  2686. $query_prefs.set('history',$history);
  2687. updateQuery();
  2688. window.location = row.find('a').attr('href') +'?'+ $query_prefs;
  2689. }
  2690. }
  2691. $dir_list_row.filter('.dir').on('dblclick', function(e) {
  2692. e.preventDefault();
  2693. e.stopPropagation();
  2694. doubleClickRow( $(this) );
  2695. });
  2696.  
  2697. //***** AUTOLOAD CONTENT: index files or files from the file shortcut list *****//
  2698. function autoSelectFile() {
  2699. if ( $UI_pref_selected !== '' ) {
  2700. selectThis( $dir_list.find('tbody tr.dir:not(.invisible)').eq($UI_pref_selected) );
  2701. showContent( $dir_list.find('tbody tr.dir:not(.invisible)').eq($UI_pref_selected) );
  2702. } else if ( $UI_pref_file !== '' ) {
  2703. clickThis( $dir_list_row.find('a:contains('+ $UI_pref_file +')') );
  2704. } else if ( $body.hasClass('autoload_index_files') && $dir_list.find( 'a[href*="/index."]').length > 0 ) { // else load index file
  2705. clickThis( $dir_list.find('a[href*="/index."]') );
  2706. } else if ( $body.hasClass('autoload_media') && $body.hasClass('has_media') ) { // else if audio and images, load cover art
  2707. clickThis( $dir_list.find('.audio,.video').first() );
  2708. } else { // else select first non-media item
  2709. // clickThis( $dir_list.find('tbody tr:not(.media):not(.invisible)').first() );
  2710. }
  2711. scrollThis('dir_list','selected');
  2712. scrollThis('dir_list','playing');
  2713. }
  2714. autoSelectFile();
  2715.  
  2716. // Autoload Cover Art
  2717. function autoLoadCoverArt() { // autoload cover art for audio if dir contains audio and images, and audio is loaded and non-image content is not loaded
  2718. if ( $body.hasClass('has_audio') && $body.hasClass('has_images') && $content_pane.hasClass('has_audio') && $content_image.siblings().attr('src') == undefined ) {
  2719. var $cover;
  2720. var $this_ext;
  2721. const $image_titles = ['cover','front'];
  2722. 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].
  2723. $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();
  2724. if ( $cover.length === 1 ) {
  2725. showContent($cover);
  2726. $title.empty().text( $cover.find('a').text() );
  2727. setDimensions($cover);
  2728. break;
  2729. } else {
  2730. showContent( $dir_list.find('.image').first() ); // otherwise show the first image
  2731. $title.empty().append($dir_list.find('.image').first().find('a').text() );
  2732. setDimensions($dir_list.find('.image').first());
  2733. break;
  2734. }
  2735. }
  2736. }
  2737. }
  2738. // File shortcuts menu: autoload directory, then auto-select file
  2739. function showFileShortcut(item) {
  2740. $this_link = thisLink(item);
  2741. window.location = $this_link.slice( 0, $this_link.lastIndexOf('/') );
  2742. }
  2743.  
  2744. //***** KEYBOARD EVENTS *****//
  2745. $body.on('keydown',$dir_list,function(e) {
  2746. setContentHeight();
  2747. $(':focus').blur(); // Need this to able to select grid items after clicking close button (or other buttons).
  2748. let $dir_list_row = $dir_list.find('tbody tr:not(.unchecked)').filter(':visible');
  2749. let $selected = $dir_list.find('tbody tr.selected');
  2750. let $selected_href = thisLink($selected);
  2751. let $playing = $dir_list_row.filter('.playing');
  2752. var $time;
  2753.  
  2754. switch ( e.key ) {
  2755.  
  2756. case 'ArrowUp':
  2757. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) { // Cmd/Ctrl + up arrow = go to parent directory
  2758. $('#parent_dir_menu a').trigger('click');
  2759. break;
  2760. }
  2761. if ( $('*[contentEditable="true"]').is(':focus') ) { // Allow arrow navigation within content_editable elements
  2762. return;
  2763. }
  2764. e.preventDefault();
  2765. arrowNavigation(e);
  2766. break;
  2767.  
  2768. case 'ArrowDown':
  2769. 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
  2770. return;
  2771. } else if ( $('*[contentEditable="true"]' ).is(':focus') ) {
  2772. return;
  2773. } else if ( (e.ctrl || e.metaKey) && $selected.hasClass('dir') ) {
  2774. $selected.find('a').trigger('dblclick');
  2775. break;
  2776. }
  2777. e.preventDefault();
  2778. arrowNavigation(e);
  2779. break;
  2780.  
  2781. case 'ArrowLeft':
  2782. if ( (e.ctrl || e.metaKey) || (e.ctrl && e.metaKey) ) {
  2783. return;
  2784. } else if ( $('*[contentEditable="true"]').is(':focus') ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable
  2785. return;
  2786. }
  2787. if ( !e.metaKey && (e.altKey && e.shiftKey) || e.altKey ) { // Skip media -30s/-10s
  2788. mediaSkip(e);
  2789. return;
  2790. } else {
  2791. arrowNavigation(e);
  2792. }
  2793. break;
  2794.  
  2795. case 'ArrowRight':
  2796. if ( e.metaKey && !e.altKey && !e.shiftKey && $selected.hasClass('dir') ) { // Open dir with Cmd/Ctrl + Right Arrow
  2797. $selected.find('a').trigger('dblclick');
  2798. return;
  2799. }
  2800. if ( (e.ctrl || e.metaKey) || (e.ctrl && e.metaKey) ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable
  2801. return;
  2802. } else if ( $('*[contentEditable="true"]').is(':focus') || $selected.hasClass('dir ignore') ) {
  2803. return;
  2804. }
  2805. if ( !e.metaKey && (e.altKey && e.shiftKey) || e.altKey ) { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  2806. mediaSkip(e);
  2807. return;
  2808. } else {
  2809. arrowNavigation(e);
  2810. }
  2811. break;
  2812.  
  2813. case ' ': // space
  2814. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2815. alphaNav(e);
  2816. }
  2817. if ( $content_pane.hasClass('has_audio') || $content_pane.hasClass('has_video') ) { // Play/pause media (space bar)
  2818. e.preventDefault();
  2819. playPauseMedia();
  2820. }
  2821. break;
  2822.  
  2823. case 'Enter': // Open directories (or ignore)
  2824. if ( $selected.hasClass('app') && $settings.apps_as_dirs === false ) {
  2825. break;
  2826. } else {
  2827. if ( $selected.hasClass('dir') ) {
  2828. $selected.find('a').trigger('dblclick');
  2829. } else if ( $content_pane.hasClass('has_grid') ) {
  2830. $body.find('#dir_list').find('.selected a').click();
  2831. } else {
  2832. clickThis(thisRow($selected));
  2833. if ( $selected.hasClass('media') && !$content_pane.hasClass('has_grid') ) { // Use return to play/pause media
  2834. let e = new Event('keydown');
  2835. e.key = ' ';
  2836. }
  2837. }
  2838. }
  2839. break;
  2840.  
  2841. // Alphabetical navigation
  2842. 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':
  2843. 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':
  2844. 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 'π':
  2845. // 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 'ϖ':
  2846. // 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 'Π':
  2847. 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 '≠':
  2848. case '⁄': case '€': case '‹': case '›': case 'fl': case '‡': case '°': case '·': case '‚': case '±': case '¯': case '˘': case '¿': case '`':
  2849. case 'ı': case '': case '´': case '': case '˝': case 'ˆ': case '': case '˜': case '‰': case 'ˇ': case '¨': case '◊': case '„': case '˛': case '¸':
  2850. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2851. alphaNav(e);
  2852. }
  2853. break;
  2854.  
  2855. case 'd': // Cmd/Ctrl + D: Toggle Details
  2856. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) {
  2857. e.preventDefault();
  2858. e.stopPropagation();
  2859. $details_btn.click();
  2860. }
  2861. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2862. alphaNav(e);
  2863. }
  2864. break;
  2865.  
  2866. case 'g': // Cmd/Ctrl + G: Show image Grid
  2867. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) {
  2868. e.preventDefault();
  2869. e.stopPropagation();
  2870. $grid_btn.click();
  2871. }
  2872. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2873. alphaNav(e);
  2874. }
  2875. break;
  2876.  
  2877. case 'i': // Cmd/Ctrl + I: Toggle Invisibles
  2878. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) {
  2879. e.preventDefault();
  2880. e.stopPropagation();
  2881. $inv_checkbox.find('input').click();
  2882. }
  2883. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2884. alphaNav(e);
  2885. }
  2886. break;
  2887.  
  2888. case 'o': // Cmd/Ctrl + Shift + O: Open selected item in new window
  2889. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2890. alphaNav(e);
  2891. }
  2892. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.shiftKey ) {
  2893. window.open($selected_href);
  2894. }
  2895. break;
  2896.  
  2897. case 'r': // Cmd/Ctrl + Shift + R: Refresh
  2898. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ) {
  2899. e.preventDefault();
  2900. e.stopPropagation();
  2901. $content_reload_btn.find('button').click();
  2902. }
  2903. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2904. alphaNav(e);
  2905. }
  2906. break;
  2907.  
  2908. case 'w': // Close content pane if Close button visible with Cmd/Crtl + W
  2909. // Doesn't work in some browsers: can't override default keybinding
  2910. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && ( $content_pane.hasClass('[class^="has_"]') || $content_pane.hasClass('has_grid') ) ) {
  2911. e.preventDefault();
  2912. e.stopPropagation();
  2913. $content_close_btn.find('button').click();
  2914. }
  2915. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2916. alphaNav(e);
  2917. }
  2918. break;
  2919.  
  2920. case '.': // Increase previewed content size
  2921. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2922. alphaNav(e);
  2923. }
  2924. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.shiftKey ) {
  2925. $('#increase').click();
  2926. }
  2927. break;
  2928.  
  2929. case ',': // Decrease previewed content size
  2930. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2931. alphaNav(e);
  2932. }
  2933. if ( (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.shiftKey ) {
  2934. $('#decrease').click();
  2935. }
  2936. break;
  2937.  
  2938. case 'tab':
  2939. break;
  2940.  
  2941. } // end switch
  2942. });
  2943. // ***** END KEYBOARD EVENTS ***** //
  2944.  
  2945. // ***** GRID SETUP ***** //
  2946. // Create Font Grid Items
  2947. const fontGridItems = function() {
  2948. var $font_grid_items_arr = [];
  2949. $dir_list_row.filter('.font').each(function() {
  2950.  
  2951. let $this_link = $(this).find('a').attr('href');
  2952. let $font_family = thisText($(this));
  2953. let $display_name = $(this).find('a').text().slice( 0,$font_family.lastIndexOf('.') ).replace(/[_|-]/g,' ');
  2954.  
  2955. if ( $font_family_arr.indexOf($font_family) == -1 ) {
  2956. $font_family_arr.push($font_family);
  2957. $font_preview_styles.append('@font-face { font-family: "'+ $font_family +'"; src: url("'+ $this_link +'"); }'); // only add style if it doesn't exist
  2958. }
  2959. $font_grid_item_el.empty().append('<p>'+ $display_name +'</p><h2 style=\'font-family: "'+ $font_family +'"\'; ><a href="'+ $this_link +'">'+ $display_name +'</a></h2>' );
  2960. $font_grid_items_arr.push($font_grid_item_el.clone());
  2961. });
  2962. return $font_grid_items_arr;
  2963. };
  2964. // Create Image Grid Items
  2965. const imageGridItems = function() {
  2966. var $image_grid_items_arr = [];
  2967. $dir_list_row.filter('.image').each(function() {
  2968.  
  2969. let $this_link = thisLink(this);
  2970. let $this_ext = thisExt(this);
  2971. let exts = $row_types.image.filter( ext => $.inArray(ext, $row_settings.ignore) == -1 );
  2972. let $display_name = $this_link.slice($this_link.lastIndexOf('/') + 1).replace(/[_|-]/g,' ');
  2973.  
  2974. if ( $.inArray( $this_ext, exts ) > -1 ) { // if this row file ext is in the image extension array
  2975. $image_grid_item_el.empty().append('<a href="'+$this_link+'"><img src="'+$this_link+'" title="'+$display_name+'" /></a>');
  2976. $image_grid_items_arr.push( $image_grid_item_el.clone() );
  2977. }
  2978. });
  2979. return $image_grid_items_arr;
  2980. };
  2981. // Make Grids
  2982. function makeGrids(e,el) {
  2983. e.stopPropagation();
  2984. closeGrid();
  2985. $content_pane.removeClass('has_hidden_grid').addClass('has_grid');
  2986. if ( el.attr('id') === 'grid_btn' ) {
  2987. $title.removeClass();
  2988. fontGridItems().length === 0 ? $content_grid.addClass('has_image_grid') : $content_grid.addClass('has_grid');
  2989. $content_grid.append( imageGridItems(), fontGridItems() );
  2990. } else if ( el.attr('id') === 'show_font_grid' ) {
  2991. $title.removeClass().addClass('font_grid');
  2992. $content_grid.addClass('has_font_grid');
  2993. $content_grid.append( fontGridItems() );
  2994. } else {
  2995. $title.removeClass().addClass('image_grid');
  2996. $content_grid.addClass('has_image_grid');
  2997. $content_grid.append( imageGridItems() );
  2998. }
  2999. }
  3000.  
  3001. // ***** SCALE PREVIEWED IMAGES & FONTS ***** //
  3002. var $em = parseInt(getComputedStyle(document.body).fontSize); // pts/em
  3003. var $font_incr = 1.125; // scale factor for font size increase/decrease
  3004. var $image_incr = 1.125;
  3005. var $image_width;
  3006. var $image_height;
  3007. var $image_scale_factor;
  3008. var $image_grid_item_size;
  3009. var $grid_scale_factor;
  3010.  
  3011. var fontSize = function(el) {
  3012. return parseFloat(el.css('font-size'));
  3013. };
  3014. // Scale Fonts
  3015. function scaleFonts(incr, y) {
  3016. if ( y === -1 ) { incr = 1/incr; }
  3017. if ( $content_pane.hasClass('has_grid') && ( $content_grid.hasClass('has_font_grid') || $content_grid.hasClass('has_grid') ) ) {
  3018. $content_grid.css({'font-size':( fontSize($content_grid)/$em * incr ) +'em'});
  3019. return;
  3020. }
  3021. if ( $content_pane.hasClass('has_font') ) {
  3022. $content_font.css({'font-size':( fontSize($content_font)/$em * incr ) +'em'});
  3023. return;
  3024. }
  3025. }
  3026. // Scale Images
  3027. function scaleImages(incr, y) {
  3028. if (y === -1 ) { incr = 1/incr; }
  3029. if ( $content_pane.hasClass('has_image') && !$content_pane.hasClass('has_grid') ) {
  3030. $image_width = parseFloat( $content_image.find('img').width() ) * incr; // increment image size
  3031. $image_height = parseFloat( $content_image.find('img').height() ) * incr; // increment image size
  3032. $image_scale_factor = parseFloat( $content_image.attr('data-image-scale-factor') ); // get scale factor
  3033.  
  3034. $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)'});
  3035.  
  3036. $content_image.scrollLeft( ( $image_width - $(window).width() )/2 ) ;
  3037. if ( ( ( $image_height - $(window).height() )/2 ) > 0 ) {
  3038. $content_image.scrollTop( ( $image_height - $(window).height() )/2 );
  3039. }
  3040. return;
  3041. }
  3042. if ( $content_pane.hasClass('has_grid') && ( $content_grid.hasClass('has_image_grid') || $content_grid.hasClass('has_grid') ) ) {
  3043. $image_grid_item_size = parseFloat( $('.image_grid_item').width() * incr);
  3044. $grid_scale_factor = parseFloat( $content_grid.attr('data-grid-scale-factor') );
  3045.  
  3046. $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))' });
  3047. $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
  3048. $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'});
  3049. return;
  3050. }
  3051. }
  3052. // Scale Fonts and Images
  3053. function scalePreviewItems(y) { // combine scaling into one function
  3054. scaleImages( $image_incr, y );
  3055. scaleFonts( $font_incr, y );
  3056. }
  3057. // Scale Content
  3058. $content_scale.on('click','span',function() {
  3059. if ( $(this).attr('id') === 'increase' ) {
  3060. scalePreviewItems(1);
  3061. }
  3062. if ( $(this).attr('id') === 'decrease' ) {
  3063. scalePreviewItems(-1);
  3064. }
  3065. });
  3066. // Zoom Images on click
  3067. function zoomImage(x,e) {
  3068. $this_link = $(x).attr('src');
  3069. var $offset = $(x).offset();
  3070. var $this_width = $(x).width();
  3071. var $this_height = $(x).height();
  3072.  
  3073. $(x).toggleClass('zoom_img');
  3074. if ( $(x).attr('style') !== '' || $(x).attr('style') !== undefined ) {
  3075. $(x).attr('style','');
  3076. }
  3077. if ( $this_link !== undefined ) {
  3078. getDimensions( $this_link, function( width, height ) {
  3079. $content_image.scrollLeft( (e.pageX - $offset.left) * width/($this_width + 13) - ( e.pageX - $offset.left ) - 52 ) ;
  3080. $content_image.scrollTop( (e.pageY - $offset.top) * height/($this_height + 13) - ( e.pageY - $offset.top ) - 52 );
  3081. });
  3082. }
  3083. }
  3084. $content_image.on('click', 'img', function(e) {
  3085. zoomImage( this,e )
  3086. });
  3087. // ***** END SCALE PREVIEW ITEMS ***** //
  3088.  
  3089. // ***** AUDIO CONTENT ***** //
  3090.  
  3091. var $count = 0;
  3092. var $playlist;
  3093. var $shuffleList = [];
  3094. var $next;
  3095. var $task;
  3096. // Update Playlist
  3097. function updatePlaylist() {
  3098. $playlist = [];
  3099. $dir_list_row.filter('.audio:not(.unchecked)').each(function() {
  3100. $this_link = thisLink(this);
  3101. $playlist.push($this_link);
  3102. });
  3103. return $playlist;
  3104. }
  3105. // Initialize Shuffle List
  3106. function initShuffleList() {
  3107. updatePlaylist();
  3108. $shuffleList = [];
  3109. for ( let i = 0; i < $playlist.length; i += 1 ) {
  3110. $shuffleList.push(i);
  3111. }
  3112. return $shuffleList;
  3113. }
  3114. // Update Play Count
  3115. function updateCount() {
  3116. $count = $playing.index('.audio:not(.unchecked)');
  3117. }
  3118. // Check/Uncheck Audio/Video Files
  3119. function toggleChecked(e) {
  3120. e.stopPropagation();
  3121. $(this).blur();
  3122. thisRow(this).toggleClass('unchecked');
  3123. updateCount();
  3124. updatePlaylist();
  3125. initShuffleList();
  3126. }
  3127. $dir_list_row.filter('.media').on('click','input', toggleChecked );
  3128. // Check/Uncheck all Audio/Video Files
  3129. function toggleAllChecked(e) {
  3130. e.stopPropagation();
  3131. $dir_list_row.find('input').trigger('click');
  3132. updatePlaylist();
  3133. initShuffleList();
  3134. }
  3135. $dir_list.find('#play_toggle').on('click', toggleAllChecked );
  3136.  
  3137. // Is Playing
  3138. function isPlaying(el) {
  3139. return (el.get(0).currentTime > 0 && !el.get(0).paused && !el.get(0).ended);
  3140. }
  3141. // Play Media
  3142. function playMedia(task) {
  3143. $('.playing').hasClass('audio') ? $audio.trigger(task) : $content_video.trigger(task);
  3144. }
  3145. // Skip media tracks +/-10/30 seconds
  3146. function mediaSkip(e) {
  3147. let factor = e.key === 'ArrowLeft' ? -1 : 1;
  3148. if ( e.altKey && e.shiftKey ) {
  3149. n = 30;
  3150. } else if ( e.altKey ) {
  3151. n = 10;
  3152. }
  3153. let $media = $('.playing').hasClass('audio') ? $audio : $content_video;
  3154. let $time = $media.prop('currentTime');
  3155. $media.prop('currentTime', $time + factor*(n));
  3156. }
  3157.  
  3158. // Play/Pause Audio/Video
  3159. function playPauseMedia() {
  3160. if ( $content_pane.hasClass('has_audio') ) {
  3161. isPlaying($audio) ? $audio.trigger('pause') : $audio.trigger('play');
  3162. }
  3163. if ( $content_pane.hasClass('has_video') ) {
  3164. isPlaying($content_video) ? $content_video.trigger('pause') : $content_video.trigger('play');
  3165. }
  3166. }
  3167. // Play Next Track
  3168. function playPrevNextTrackBtn(el) {
  3169. e = $.Event("keydown");
  3170. e.key = el.attr('id') === "prev_track" ? 'ArrowLeft' : 'ArrowRight';
  3171. playPrevNextTrack(e);
  3172. }
  3173. // Prev/Next Track buttons
  3174. $('.prev_next_track_btn').on( 'click', function() { playPrevNextTrackBtn( $(this) ); });
  3175.  
  3176. // Toggle Shuffle Play
  3177. function toggleShuffle() {
  3178. let $rand = Math.floor( Math.random() * ($media.length) );
  3179. if ( $shuffle.find('input').prop('checked') ) {
  3180. ( !isPlaying($audio) && !isPlaying($content_video) ) ? clickThis( $media.eq($rand) ) : null; // if no media playing, select random track; if media playing, do nothing, then shuffle.
  3181. updatePlaylist();
  3182. initShuffleList();
  3183. $count = Math.floor( Math.random() * $shuffleList.length );
  3184. }
  3185. }
  3186. $shuffle.on('click', toggleShuffle );
  3187.  
  3188. // Shuffle Play
  3189. function shuffle() {
  3190. if ( $shuffleList.length <= 1 && $loop.find('input').prop('checked') ) { // If loop is checked, and all songs have been shuffled...
  3191. initShuffleList();
  3192. $next = Math.floor( Math.random() * $shuffleList.length ); // choose a random index number from the remaining unplayed songs,
  3193. $count = $shuffleList[ $next ]; // set the playing $count to the index
  3194. } else if ( $shuffleList.length === 0 ) { // else if all songs have been shuffled, select the first song and stop;
  3195. $count = 0;
  3196. } else {
  3197. $next = Math.floor( Math.random() * $shuffleList.length ); // else choose a random index number from the remaining unplayed songs,
  3198. $count = $shuffleList[ $next ]; // set the playing $count to the index
  3199. $shuffleList.splice( $next, 1 ); // remove the indexed value from the unplayed list,
  3200. }
  3201. }
  3202. // Initialize Audio
  3203. function initMedia() {
  3204. $('#audio, #content_video').on('ended', function() {
  3205. playPrevNextTrack('ArrowRight');
  3206. scrollThis('dir_list','playing');
  3207. });
  3208. }
  3209. initMedia();
  3210. // ***** END AUDIO CONTENT ***** //
  3211.  
  3212. // ***** OTHER FUNCTIONS ***** //
  3213.  
  3214. // Resize Sidebar/Content Pane
  3215. function resizeSidebar(f) {
  3216. f.stopPropagation();
  3217. var $startX = f.pageX;
  3218. var $sidebar_width = $sidebar_wrapper.width();
  3219. var $window_width = window.innerWidth;
  3220. $sidebar_overlay.show(); // needed to prevent interactions with iframe
  3221. $content_overlay.show(); // needed to prevent interactions with iframe
  3222. $sidebar_wrapper.css({'-webkit-user-select':'none','-moz-user-select':'none','user-select':'none'});
  3223.  
  3224. $(document).on('mousemove',function(e) {
  3225. e.stopPropagation();
  3226. var $deltaX = e.pageX - $startX;
  3227. if ( e.pageX > 200 && e.pageX < $window_width - 200 ) {
  3228. $sidebar_wrapper.css({'width':$sidebar_width + $deltaX + 'px'});
  3229. $content_pane.css({'width':($window_width - $sidebar_width) - $deltaX + 'px'});
  3230. }
  3231. setContentHeight();
  3232. });
  3233. $(document).on('mouseup',function() {
  3234. $sidebar_overlay.hide(); // needed to prevent interactions with iframe
  3235. $content_overlay.hide();
  3236. $sidebar_wrapper.css({'-webkit-user-select':'auto','-moz-user-select':'auto','user-select':'auto'});
  3237. $(document).off('mousemove');
  3238. $sidebar_width = $sidebar_wrapper.width();
  3239. $query_prefs = getQueryPrefs();
  3240. $query_prefs.set('width',$sidebar_width);
  3241. updateQuery();
  3242. });
  3243. }
  3244. $handle.on('mousedown', resizeSidebar );
  3245.  
  3246. // EXPORT SETTINGS
  3247. let $settings_string = unescape( $settings.toSource() );
  3248. $settings_string = $settings_string.replace('JSON.parse\(unescape\("\{','').replace('\}"))','').replace(/\/",/g,'",').replace(/\/"],/g,'"],').replace(/"(\w+?)":/g,'\n\n$1:\t');
  3249.  
  3250. var save = function(filename, data) {
  3251. var blob = new Blob([data], {type: 'text/html'});
  3252. if ( window.navigator.msSaveOrOpenBlob ) {
  3253. window.navigator.msSaveBlob( blob, filename );
  3254. } else {
  3255. var elem = window.document.createElement('a');
  3256. elem.href = window.URL.createObjectURL(blob);
  3257. elem.download = filename;
  3258. document.body.appendChild(elem);
  3259. elem.click();
  3260. document.body.removeChild(elem);
  3261. URL.revokeObjectURL(blob);
  3262. }
  3263. }
  3264.  
  3265. $export_settings.on('click','a',function(e) {
  3266. e.preventDefault();
  3267. save("settings.txt",$settings_string);
  3268. });
  3269. })();