Supercharged Local Directory File Browser

Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds sidebar and preview pane; keyboard navigation for directory items; audio playback with shuffle and loop; video player; edit, preview, and save markdown/plain text files; preview images and fonts; image and font grids; sorting; user-defined shortcuts; more.

As of 2019-08-11. See the latest version.

  1. // ==UserScript==
  2. // @name Supercharged Local Directory File Browser
  3. // @version 4.0.6.1
  4. // @description Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds sidebar and preview pane; keyboard navigation for directory items; audio playback with shuffle and loop; video player; edit, preview, and save markdown/plain text files; preview images and fonts; image and font grids; sorting; user-defined shortcuts; more.
  5. // @author Gaspar Schott (Michael Schrauzer) mshroud@gmail.com
  6. // @license GPL-3.0-or-later
  7. // @homepageURL https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser
  8. // @contributionURL https://paypal.me/mschrauzer
  9. // @include file://*
  10. // @include about:blank
  11.  
  12. // @require https://code.jquery.com/jquery-latest.min.js
  13.  
  14. // @require https://cdnjs.cloudflare.com/ajax/libs/markdown-it/9.0.1/markdown-it.js
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/markdown-it-footnote/3.0.2/markdown-it-footnote.min.js
  16. // @require https://cdn.jsdelivr.net/npm/markdown-it-toc-done-right@2.1.0/dist/markdown-it-toc-made-right.min.js
  17. // @require https://cdn.jsdelivr.net/npm/markdown-it-sub@1.0.0/dist/markdown-it-sub.min.js
  18. // @require https://cdn.jsdelivr.net/npm/markdown-it-sup@1.0.0/dist/markdown-it-sup.min.js
  19. // @require https://cdn.jsdelivr.net/npm/markdown-it-deflist@2.0.3/dist/markdown-it-deflist.min.js
  20. // @require https://cdn.jsdelivr.net/npm/markdown-it-multimd-table@3.2.3/dist/markdown-it-multimd-table.min.js
  21. // @require https://cdn.jsdelivr.net/npm/markdown-it-center-text@1.0.4/dist/markdown-it-center-text.min.js
  22.  
  23.  
  24. // NOTE: This script was developed in Vivaldi, running on Mac OS High Sierra. It has been tested in various Chrome and Gecko-based browsers.
  25. // It has been minimally tested on Windows and not at all on other OSes. It should work, but please report any issues.
  26. // 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).
  27.  
  28. // 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.
  29. // For Tampermonkey, go to Chrome extension page, and tick the 'Allow access to file URLs' checkbox at the Tampermonkey extension section.
  30. // For Greasemonkey, open about:config and change greasemonkey.fileIsGreaseable to true.
  31.  
  32. // @namespace https://greatest.deepsurf.us/users/16170
  33. // ==/UserScript==
  34.  
  35. (function() {
  36. 'use strict';
  37. const $ = window.jQuery;
  38.  
  39. // ***** USER SETTINGS ***** //
  40.  
  41. const $settings = {
  42. // Paste your exported settings between the two lines below:
  43. //--------------------------------------------------------//
  44.  
  45. shortcuts: // N.B.: Directory links must end with "/", file links must end with another character.
  46. // You may add as many menus and links as you like; just copy the example below and edit as needed.
  47. // Local directory shortcuts must begin with "file:///"; external shortcuts must begin with the correct protocol ("http://" or "ftp://", etc.).
  48. // Note that because of same-origin security concerns, the browser will not allow you to navigate directly from an external webpage to a local directory.
  49. [
  50. {
  51. "menu_title":"My Sample Menu",
  52. "links":
  53. [
  54. { "link_name":"My Directory Link 1", "link":"file:///Path/To/My/Directory/" },
  55. { "link_name":"My Directory Link 2", "link":"file:///Path/To/My/Directory_2/" },
  56. { "link_name":"My External Link", "link":"https://www.mywebpage.com/" },
  57. { "link_name":"My File Link", "link":"file:///Path/To/My/File.ext" },
  58. ]
  59. },
  60. {
  61. "menu_title":"My Second Sample Menu",
  62. "links":
  63. [
  64. { "link_name":"My Directory Link 1", "link":"file:///Path/To/My/Directory/" },
  65. { "link_name":"My Directory Link 2", "link":"file:///Path/To/My/Directory_2/" },
  66. { "link_name":"My External Link", "link":"https://www.mywebpage.com/" },
  67. { "link_name":"My File Link", "link":"file:///Path/To/My/File.ext" },
  68. ]
  69. },
  70. ],
  71. // GENERAL USER SETTINGS
  72. alternate_background: true, // If true (default true), alternate sidebar row background color.
  73. apps_as_dirs: false, // Un*x/Mac OS only: if true, treat apps as directories; allows app contents to be browsed. This is the default behavior for Chrome.
  74. // If false (default), treat apps as ignored files.
  75. autoload_media: true, // If true (default: true), the first audio or video file found in a directory will be automatically selected and loaded for playback; also, cover art (if any, will be loaded in the preview pane).
  76. autoload_index_files: false, // If true (default: false), automatically select first "index.xxx" (.xxx !== .htm) file found in directory.
  77. // Note: the browser will automatically load any index.html files it finds in the directory, so the script will not work properly in such cases.
  78. theme: 'light', // Options: 'light' or 'dark'
  79. sort_by: 'default', // Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
  80. // default = Chrome sorting: dirs on top, files alphabetical.
  81. dirs_on_top: false, // If true, directories will always be listed firs except when sorting by "name" (since otherwise sorting by "name" would equal "default").
  82. // 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.)
  83. grid_font_size: 1, // Default = 1
  84. grid_image_size: 184, // Default = 184 (200px - 16px)
  85. show_details: true, // If true (default), hide file and directory details; if false, show them.
  86. show_ignored_files: false, // If true, ignored files will appear greyed-out.
  87. // If false (default), ignored files will be completely hidden from the file list;
  88. ignore_ignored_files: true, // 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;
  89. // If false, ignored files will be treated like 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 but may be useful in some circumstances).
  90. show_invisibles: true, // Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
  91. show_numbers: true, // If true (default true), number index items
  92. UI_font: 'system-ui, sans-serif', // Choose an installed font for the UI; if undefined, use browser defaults instead.
  93. UI_font_size: '13px', // Choose a default UI font size; use any standard CSS units.
  94. use_custom_icons: true, // if true (default), use custom icons for dirs and files
  95. // if false, use browser/server default icons
  96. // TEXT EDITING SETTINGS
  97. enable_text_editing: true, // If true (default), allow plain text files to be edited.
  98. default_text_view: 'preview_text', // Options: 'source_text' or 'preview_text' for text editor.
  99. // Note that split_view = true overrides this setting.
  100. split_view: true, // If true, show split view on plain text file load.
  101. // if true (default), use default preview_text setting.
  102. sync_scroll: true // If true (default: true), show split view on plain text file load
  103. // if false, use default preview_text setting.
  104. //--------------------------------------------------------//
  105. // Paste your exported settings between the above two lines.
  106. };
  107.  
  108. // $ROW_TYPES:
  109. // DO NOT DELETE ANY EXISTING CATEGORIES!
  110. // Add file extensions for sorting and custom icon display to the existing categories.
  111. // You can also define your own new categories, but do not add an extension to more than one row_type category.
  112. // Do not add leading "." to the extensions.
  113. const $row_types = {
  114. // myRowType: ['ext1','ext2'],
  115. dir: ['/'],
  116. app: ['app/','app','exe','msi'],
  117. archive: ['7z','archive','bz2','cbr','dmg','gz','pkg','rar','tar','zip'],
  118. audio: ['aac','aif','aiff','ape','flac','m4a','mp3','ogg','opus','wav','m3u'],
  119. bin: ['dll','dylib','icc','msi'],
  120. code: ['bak','bash','bash_profile','bashrc','c','cfg','cnf','codes','coffee','conf','csh','cshrc','cson','css','custom_aliases','default','dist','editorconfig','emacs','example','gemspec','gitconfig','gitignore','gitignore_global','h','hd','ini','js','json','jsx','less','list','local','login','logout','lua','mkshrc','old','php','pl','plist','pre-oh-my-zsh','profile','pth','py','rb','rc','rdoc','sass','settings','sh','strings','taskrc','tcl','viminfo','vimrc','vue','xml','yaml','yml','zlogin','zlogout','zpreztorc','zprofile','zsh','zshenv','zshrc'],
  121. database: ['db','sql', 'sqlite'],
  122. font: ['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'],
  123. graphics: ['ai','book','dtp','eps','fm','icml','idml','indd','indt','inx','mif','pmd','pub','qxb','qxd','qxp','sla','swf'],
  124. htm: ['htm','html','xhtm','xhtml'],
  125. image: ['apng','bmp','gif','jpeg','jpg','png','svg','webp'],
  126. ignored_image: ['ai','arw','cr2','dng','eps','nef','psd','psd','raw','tif','tiff'],
  127. markdown: ['md','markdown','mdown','mkdn','mkd','mdwn','mdtxt','mdtext'],
  128. office: ['csv','doc','docx','epub','key','numbers','odf','ods','odt','pages','rtf','scriv','xls','xlsx','xlm'],
  129. pdf: ['pdf'],
  130. system: ['DS_Store','ds_store','icon','ics'],
  131. text: ['log','nfo','txt','m3u'],
  132. video: ['m4v','mov','mp4','mpeg','webm']
  133. };
  134.  
  135. // $ROW_SETTINGS: Ignore or Exclude files by extension
  136. const $row_settings = {
  137. // Ignore: $row_types or files with extensions added here will not be loaded if selected in the sidebar (prevents the browser from attempting to download the file).
  138. ignore: $row_types.archive.concat( 'alias', $row_types.bin, $row_types.database, $row_types.graphics, $row_types.ignored_image, $row_types.office, $row_types.system),
  139. // Exclude: Files with these exensions will not be inverted in dark mode
  140. exclude: ['htm','html','xhtm','xhtml']
  141. };
  142.  
  143. // ***** END USER SETTINGS ***** //
  144.  
  145. // ## FEATURES INCLUDE:
  146. // - Resizable sidebar and directory/file preview pane.
  147. // - Arrow navigation in sidebar:
  148. // - Up and Down Arrows select next/prev item.
  149. // - Left and Right Arrows select next/prev item of same type.
  150. // - Navigate sidebar by typed string.
  151. // - Show/Hide file details (size (if avail), date modified (if avail), kind, extension).
  152. // - Sort sidebar items by name or file details.
  153. // - Default sort = sort by name with folders on top.
  154. // - Preview all file types supported by browser (html, text, images, pdf, audio, video, etc.) and preview fonts.
  155. // - Preview and edit markdown and plain text files, with option to save files locally.
  156. // - Markdown rendered with markdownit.js ( https://github.com/markdown-it/markdown-it ).
  157. // - Uses Github Markdown styles for preview ( https://github.com/sindresorhus/github-markdown-css ), with a few customizations.
  158. // - Support for:
  159. // - TOC creation ( `${toc}` ) ( https://github.com/nagaozen/markdown-it-toc-done-right )
  160. // - Multimarkdown table syntax ( https://github.com/RedBug312/markdown-it-multimd-table )
  161. // - Live checkboxes ( `\[ ], [x]` ), allowed in lists and deflists.
  162. // - Superscript ( `^sup^` ) ( https://github.com/markdown-it/markdown-it-sup )
  163. // - Subscript ( `~sub~` ) ( https://github.com/markdown-it/markdown-it-sub )
  164. // - Definition lists ( https://github.com/markdown-it/markdown-it-deflist; for syntax, see http://pandoc.org/MANUAL.html#definition-lists )
  165. // - Centered text ( `->centered<-` ) ( https://github.com/jay-hodgson/markdown-it-center-text )
  166. // - Footnotes ( https://github.com/markdown-it/markdown-it-footnote )
  167. // - View source text, preview, or split pane with proportional sync scroll.
  168. // - Save edited source text or previewed HTML.
  169. // - Create and edit text in separate text editor.
  170. // - Audio and video playback, with shuffle, loop, skip audio +/- 10 or 30 sec via keyboard.
  171. // - Preview other files (e.g., lyrics or cover art) in same directory while playing audio.
  172. // - User setting to autoload cover art (if any images in directory, load "cover.ext" or first image found)
  173. // - Grid view for images and fonts.
  174. // - User settings (see $settings in code; some settings can be changed via the main menu in the UI and will be remembered in URL query):
  175. // - Light or Dark theme.
  176. // - Bookmarks for local or remote directories.
  177. // - Default image grid size.
  178. // - Default UI font size and font-family.
  179. // - Default UI font and font-size.
  180. // - Default file sorting.
  181. // - Sort with directories on top.
  182. // - Treat apps as directories (MacOS and *nix only)
  183. // - Show or hide invisible files.
  184. // - Show or hide ignored files in the ignored files list (see $row_settings in code below $settings).
  185. // - Show or hide file details.
  186. // - Use custom file icons or browser defaults.
  187. // - Autoload index.ext files.
  188. // - Autoload cover art in directories with audio files.
  189. // - Text editing default view: split, source, or preview.
  190. // - Text editing sync scroll: on or off.
  191.  
  192. // ## KEYBINDINGS (These don't work in all browsers):
  193.  
  194. // - <kbd>Arrow Up/Down</kbd>: Select prev/next item.
  195. // - If audio is playing, and prev/next file is also audio, it will be highlighted but not loaded in the audio player; press return to load it.
  196. // - <kbd>Arrow Left/Right</kbd>: Select prev/next row of the same kind as the current selection.
  197. // - If current selection is a media file, select and begin playback of the next media item.
  198. // - <kbd>Opt/Alt + Arrow Left/Right</kbd>: Skip audio ±10s
  199. // - <kbd>Opt/Alt + Shift + Arrow Left/Right</kbd>: Skip audio ±30s
  200. // - <kbd>Cmd/Ctrl + Arrow Up</kbd>: Go to parent directory
  201. // - <kbd>Cmd/Ctrl + Arrow Down</kbd>: Open selected directory
  202. // - <kbd>Return</kbd>: Open selected directory, select file, or pause/play media.
  203. // - <kbd>Space</kbd>: Pause/Play media files
  204. // - <kbd>Cmd/Ctrl + D</kbd>: Toggle file details (size, date modified) in some index page types.
  205. // - <kbd>Cmd/Ctrl + E</kbd>: Show text editor.
  206. // - <kbd>Cmd/Ctrl + G</kbd>: Show or Reset Grid.
  207. // - <kbd>Cmd/Ctrl + I</kbd>: Toggle Invisibles.
  208. // - <kbd>Cmd/Ctrl + Shift + O</kbd>: Open selected item in new window/tab.
  209. // - <kbd>Cmd/Ctrl + R</kbd>: Reload grids and previewed content, reset scaled images/fonts, reset media files to beginning.
  210. // - <kbd>Cmd/Ctrl + W</kbd>: Close previewed content (doesn't work in all browsers; use close button instead), or close window if no content is being previewed.
  211. // - <kbd>Cmd/Ctrl + Shift + < or ></kbd>: Scale preview items and grids.
  212.  
  213. // CHANGELOG:
  214.  
  215. // **VERSION 4.0**
  216. // **FIXED:** An issue with video display.
  217. // **FIXED:** Several styling issues in Firefox, including textarea in text editor not expanding to full window height.
  218. // **FIXED:** An issue with some servers that add a trailing '/' to the query string.
  219. // **IMPROVED:** Updated markdown-it to v9.0.1; also updated some markdown-it plugins.
  220. // **NEWS:** Version 4.1, with a new feature of interest to designers, is in development.
  221.  
  222. // **VERSION 4.0.5b** Last few changes...?
  223. // **IMPROVED:** Clicking "Parent Directory" link in previewed directories now remembers sorting and numbering prefs.
  224. // **FIXED:** Many bugs with close content behavior.
  225. // **FIXED:** Several bugs with sorting items by size.
  226. // **FIXED:** Various minor styling issues and tweaks.
  227.  
  228. // **VERSION 4.0.4b** Wrapping things up....
  229. // **FIXED:** Left/Right arrow key navigation failed for video files
  230. // **FIXED:** Content preview title wasn't being set for autoloaded images.
  231. // **FIXED:** An issue with autoloading media and cover art when the first media file was a video.
  232. // **FIXED:** Various issues with parsing local directory indexes in Firefox.
  233. // **FIXED:** Various issues with parsing, calculating, and displaying file size and date-modified information.
  234. // **IMPROVED:** Attempted to make index-type detection and index prep more generic, so that the script will work with more sites "right out of the box."
  235. // **IMPROVED:** Made index prep much more efficient, especially for table-type index pages and directories with many items.
  236. // **IMPROVED:** Better error handling for directories with certain percent-encoded characters in file names.
  237. // **IMPROVED:** Changed order of sorting headers to better align with sort data in list.
  238. // **OTHER:** More minor styling issues.
  239.  
  240. // **VERSION 4.0.3b**
  241. // **ADDED:** Preview current directory index source: click the double-caret icon in the sidebar stats footer to toggle preview.
  242. // This is useful for server configurations that include information in addition to the index itself and is quicker than opening a separate "View source" tab.
  243. // **FIXED:** An issue with content display caused by the fix for pdf display in the previous version.
  244. // **FIXED:** Up directory navigation didn't correctly account for invisible files and directories when selecting previously selected directory.
  245. // **FIXED:** More issues with prepping "pre"-type index pages.
  246. // **FIXED:** Some issues with Error page display.
  247. // **FIXED:** Various minor styling issues.
  248. // **CHANGED:** Moved "Disable Text Editing" menu item to top level.
  249.  
  250. // **VERSION 4.0.2b**
  251. // **FIXED:** Pdf display (I hope).
  252.  
  253. // **VERSION 4.0.1b**
  254. // **FIXED:** Audio files weren't being loaded.
  255. // **FIXED:** Script would fail if a file name included a special regex character `[\^$.|?*+()`.
  256. // **FIXED:** Various issue with parsing "pre"-type index pages.
  257. // **IMPROVED:** Sorting and display of items with names beginning with white spaces.
  258. // **IMPROVED:** More efficient and robust index type detection.
  259. // **ADDED:** Sorting for previewed directory contents. Initial sorting is the same as the parent's current sort pref.
  260. // **ADDED:** Custom icons and numbering for previewed directory contents.
  261. // **ADDED:** Append some server info and other provided information to the stats footer.
  262. // **NOTE** that there is an issue with Chrome and pdf files.
  263.  
  264. // **VERSION 4.0.0b**
  265. // Numerous additions, improvements, and massive internal changes. Virtually no line of code has been left untouched.
  266. // **"b" === "bugs" likely. Updates will be coming.
  267. // **IMPORTANT:** This version uses a new format for user settings in the code; you will have to re-enter your defaults manually after updating.
  268. // - Do NOT use exported settings from any earlier version of the script.
  269. // **NEW:** Added independent text editor pane.
  270. // - Invoke with Cmd/Ctrl + E, or in main menu > Text Editing > Toggle Text Editor", or under "Show Details".
  271. // - To prevent loss of work (and for convenience, e.g., to refer to a file while writing), previously entered text will not be cleared when the "New" button is clicked again, when the "Close" button is clicked, or when a sidebar item is selected.
  272. // - Use the Save button to save the text as a text or html file, which can be previewed and edited as usual by navigating to its saved location.
  273. // **ADDED:** Option to number index items, with new user setting.
  274. // **ADDED:** Stats footer in sidebar showing dir and file count.
  275. // **ADDED:** Icons for shortcuts menu to indicate local vs. remote links.
  276. // **ADDED:** Text editing: Clear button to empty text editing pane.
  277. // **CHANGED:** Text editing: Github styling customizations: Make table cells top-aligned, darker blockquote text color.
  278. // **IMPROVED:** Text editing: When saving rendered HTML, header uplinks will be removed.
  279. // **IMPROVED:** Text editing: Don't close other previewed content when making new text editing pane.
  280. // **IMPROVED:** Text editing: Much improved warning system for edited text: now uses `postMessage` to communicate between iframe and parent.
  281. // **IMPROVED:** Content display: use the same code to prep and style directory contents in the preview pane.
  282. // **IMPROVED:** Live sorting of grid items (but images and fonts are still sorted separately).
  283. // **IMPROVED:** Image zoom positioned accurately under click.
  284. // **IMPROVED:** Better handling of 404 Errors (Page not found): server error message will appear in sidebar, warning icon in content pane.
  285. // **IMPROVED:** File URLs: If a file URL (instead of a directory) is entered directly in the browser URL bar, the parent directory will be loaded instead and the file automatically selected.
  286. // **IMPROVED:** Navigation: modifed highlighting styles to show when a non-audio item is selected for navigation vs. when it is loaded in preview pane but not selected (e.g. click a directory and then click an audio file; the directory item will be dimmed).
  287. // **IMPROVED:** Audio: Greatly simplified shuffle play code and fixed several bugs; shuffle list now correctly updates when individual audio tracks are checked or unchecked.
  288. // **CHANGED:** Moved scale buttons and prev/next image buttons to preview title bar.
  289. // **CHANGED:** Sort by "Name" always sorts files and directories together; "Default" sort always keeps files and directories separate.
  290. // **FIXED:** Text editing: Some issues with split-pane resizing.
  291. // **FIXED:** Cmd/Ctr+W (close) should work now: previewed content will be closed first, then audio, then the browser tab itself.
  292. // **FIXED:** Don't autoload audio cover art when next audio track begins; i.e., leave open whatever file is being previewed, including cover art or lyrics.
  293. // **FIXED:** An issue with sorting by size.
  294. // **FIXED:** An issue with the user UI font setting.
  295. // **FIXED:** Navigation by typed string now correctly scrolls directory list.
  296. // **OTHER:** Many small style fixes and tweaks. Among others, make the previewed image and image grid background color light in default theme.
  297. // **INTERNAL:** Complete code overhaul.
  298. // - Completely rewrote the code that preps the served directory index for processing by the script.
  299. // - Completely rewrote and simplied the sorting code.
  300. // - Alphabetized User Settings for easier editing.
  301. // - Fixes for additional server index configurations.
  302. // – Performance improvments (e.g., removed multiple calls to various functions, reduced initial DOM manipulation, etc.).
  303. // - General code cleanup (e.g., removed unused variables and unnecessary globals, named many formerly anonymous functions, fixed numerous syntax errors, etc.).
  304. //
  305. // **STILL TO COME:**
  306. // - Dark mode for text editing.
  307. // - Additional code cleanup, and more.
  308.  
  309. // ***** GENERAL SETUP ***** //
  310.  
  311. // ************************************ //
  312. // DON'T EDIT ANYTHING BELOW THIS LINE. //
  313. // ************************************ //
  314.  
  315. // PATHS
  316. // Fix "%" error in file name; see https://stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser
  317. function decodeURIComponentSafe(s) {
  318. if ( !s ) { return s; }
  319. return decodeURIComponent(s.replace(/%(?![0-9a-fA-F]{2})/g, '%25') ); // replace % with %25 if not followed by two a-f/number
  320. }
  321.  
  322. const $protocol = window.location.protocol;
  323. const $origin = $protocol +'//'+ window.location.host;
  324. const $location = decodeURIComponentSafe( [location.protocol, '//', location.host, location.pathname].join('') );
  325. const $current_dir_path = $location.replace(/[\/|_|—]/g,'/<wbr>').replace(/\\/g,'/'); // URL w/o query string for display
  326.  
  327. function escapeStr(str) { str = str.replace(/([\^\$\|\?\*\+\(\)\[])/g,'\$1'); }
  328.  
  329. // if URL is a file, change window location to parent dir, add querystring of file name; then autoload file.
  330. function loadFile() {
  331. if ( $location.slice($location.lastIndexOf('/')).indexOf('.') !== -1 && !$location.endsWith('/') && window.top === window.self ) {
  332. let $query_prefs = getQueryPrefs();
  333. $query_prefs.set( 'file', $location.slice($location.lastIndexOf('/') + 1) );
  334. window.location = $location.slice(0,$location.lastIndexOf('/') + 1) +'?'+ $query_prefs;
  335. return;
  336. }
  337. }
  338. loadFile();
  339.  
  340. // QUERY PREFS
  341. function getQueryPrefs() { return new URL(window.location).searchParams; }
  342. const initialQueryPrefs = getQueryPrefs();
  343. // set query key/value
  344. function setQuery(key, value) {
  345. let $query_prefs = getQueryPrefs();
  346. $query_prefs.set( key, value );
  347. updateQuery($query_prefs);
  348. }
  349. // get query value
  350. function getQuery(key) {
  351. let $query_prefs = getQueryPrefs();
  352. let value = '';
  353. if ( key === 'width' ) {
  354. value = ( !$query_prefs.has(key) ? 30 : Math.round(100 * Number.parseInt($query_prefs.get('width'))/window.innerWidth) ); // number string
  355. } else {
  356. value = ( $query_prefs.has(key) ? $query_prefs.get(key) : $settings[key] !== undefined ? $settings[key].toString() : '' );
  357. value = value.replace('%2F','').replace('/',''); // some servers add a '/' to end of query string
  358. }
  359. return value;
  360. }
  361. // toggle query key
  362. function toggleQuery(key) {
  363. let $query_prefs = getQueryPrefs();
  364. let nonBoolPrefs = {
  365. 'theme_light':{'theme':'dark'},
  366. 'theme_dark':{'theme':'light'},
  367. 'source_text':{'default_text_view':'preview_text'},
  368. 'preview_text':{'default_text_view':'source_text'},
  369. 'sort_by_default':{'sort_by':'default'},
  370. 'sort_by_name':{'sort_by':'name'},
  371. 'sort_by_size':{'sort_by':'size'},
  372. 'sort_by_date':{'sort_by':'date'},
  373. 'sort_by_kind':{'sort_by':'kind'},
  374. 'sort_by_ext':{'sort_by':'ext'},
  375. };
  376. var value, queryValue, settingsValue;
  377. if ( nonBoolPrefs[key] !== undefined ) {
  378. value = Object.values(nonBoolPrefs[key]).toString();
  379. key = Object.keys(nonBoolPrefs[key]).toString(); // must come after value: i.e., don't redefine key before getting value
  380. if ( $settings[key] === value ) { $query_prefs.delete( key ); } else { $query_prefs.set( key, value ); }
  381. } else {
  382. queryValue = $query_prefs.get(key);
  383. settingsValue = $settings[key];
  384. value = ( queryValue === null ? settingsValue.toString() : queryValue.toString() );
  385. value = ( value === 'true' ? 'false' : 'true' );
  386. if ( ( queryValue !== null && queryValue !== settingsValue ) ) {
  387. $query_prefs.delete( key );
  388. } else {
  389. $query_prefs.set( key, value );
  390. }
  391. }
  392. updateQuery($query_prefs);
  393. }
  394. // remove query key
  395. function removeQuery(key) {
  396. let $query_prefs = getQueryPrefs();
  397. $query_prefs.delete(key);
  398. updateQuery($query_prefs);
  399. }
  400. // update query string
  401. function updateQuery(querystr) {
  402. querystr = querystr.toString().replace('%2F','').replace('/','');
  403. window.history.replaceState({}, document.title, window.location.pathname +'?'+ querystr);
  404. updateParentLinks();
  405. }
  406.  
  407. // ***** SET UP UI ELEMENTS ***** //
  408.  
  409. // ***** SIDEBAR ELEMENTS ***** //
  410. const $parent_dir_menu = $('<nav id="parent_dir_menu"><a href="">&nbsp;</a></nav>');
  411. const $parents_dir_menu = $('<nav id="parents_dir_menu"><div></div></nav><ul class="menu"></ul>');
  412. const $shortcuts_menu = $('<nav id="shortcuts_menu"><div>&nbsp;</div></nav><ul id="shortcuts" class="menu"></ul>');
  413. const $show_details = $('<button class="toggle_UI_pref" id="show_details" tabindex="-1"><span>Show details</span><span>Hide details</span></button>');
  414. const $inv_checkbox = $('<label ><input class="toggle_UI_pref" type="checkbox" id="show_invisibles" for="inv_checkbox" name="inv_checkbox" tabindex="-1" />Show Invisibles</label>');
  415. 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>');
  416. const $sidebar_header = $('<table id="sidebar_header"><thead><tr id="sidebar_title"><th colspan="3">INDEX OF</th></tr></thead><tbody><tr id="sidebar_menus"><td></td><td></td><td></td></tr><tr id="sidebar_buttons"><td colspan="3"></td></tr></tbody></table>');
  417. const $text_editor_item = $('<tr id="text_editor_row"><td colspan="6"><a href="#" title="Toggle Text Editor">Text Editor</a></td></tr>');
  418. const $dir_list_head = $('<thead id="thead"><tr id="theader" class="header"><th class="toggle_UI_pref name sorting" id="sort_by_name"><span><input id="play_toggle" type="checkbox" tabindex="-1" checked="true" />Name</span></th><th class="toggle_UI_pref sorting" id="sort_by_default"><span>Default</span></th><th class="toggle_UI_pref details sorting" id="sort_by_ext"><span>Ext</span></th><th class="toggle_UI_pref details sorting" id="sort_by_size"><span>Size</span></th><th class="toggle_UI_pref details sorting" id="sort_by_date"><span>Date</span></th><th class="toggle_UI_pref details sorting" id="sort_by_kind"><span>Kind</span></th></tr></thead>');
  419. const $dir_list_body = $('<tbody id="tbody"></tbody>');
  420. var $dir_list = $('<table id="dir_list"></table>');
  421. const $sidebar = $('<div id="sidebar"></div>');
  422. const $handle = $('<div id="handle"></div>');
  423. const $sidebar_wrapper = $('<td id="sidebar_wrapper"></td>');
  424. const $toggle_sidebar = $('<div id="toggle_sidebar"></div>');
  425. const $tfoot = $('<tfoot id="tfoot"><tr id="index_stats"><td id="stats"></td><td id="toggle_info" data-kind="info" title="View current directory index source">&nbsp;</td></tr></tfoot>');
  426. const $warnings = $('<div id="warnings"><h3>Warning:</h3><p id="warning_unsaved">You have unsaved changes.</p><p id="warning_clear">Are you sure you want to clear all your text?</p><p id="warning_local">Can\'t load local directories or files from non-local web pages. Use your browser\'s bookmarks or enter the URL manually.</p><div><button id="warning_ignore_btn">Don\'t Save</button><button id="warning_cancel_btn" >Cancel</button><button id="warning_clear_btn">Clear</button><button id="warning_save_btn">Save</button><button id="warning_ok_btn">OK</button></div></div>');
  427. const $overlay = $('<div id="overlay"></div>');
  428.  
  429. // ***** CONTENT PANE ELEMENTS ***** //
  430. const $content_audio_title = $('<tr id="content_audio_title"><td colspan="3"></td></tr>');
  431. const $content_audio = $('<tr id="content_audio"><td colspan="3"></td></tr>');
  432. const $prev_track = $('<div id="prev_track" class="prev_next_track_btn" title="Previous track">&nbsp;</div>');
  433. const $next_track = $('<div id="next_track" class="prev_next_track_btn" title="Next track">&nbsp;</div>');
  434. const $audio_player = $('<audio id="audio" preload="auto" tabindex="0" controls >Sorry, your browser does not support HTML5 audio.</audio>');
  435. const $loop = $('<label><input type="checkbox" id="loop" for="loop" name="loop" tabindex="0" />Loop</label>');
  436. const $shuffle = $('<label><input type="checkbox" id="shuffle" for="shuffle" name="shuffle" tabindex="0" />Shuffle</label>');
  437. const $close_audio = $('<div id="close_audio" title="Close audio"></div>');
  438. const $content_grid = $('<div id="content_grid" data-grid-scale-factor="1" data-kind="grid"></div>');
  439. const $content_text = $('<div id="content_text"></div>');
  440. const $sample_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789 [(!@#$%^&*;:)]';
  441. const $hamburger_string = '<h1>Typography</h1><h4>The art of using types to produce impressions on paper, vellum, &amp;c.</h4><h2>S P E C I M E N</h2><h3>Typography is the work of typesetters (also known as compositors), typographers, graphic designers, art directors, manga artists, comic book artists, graffiti artists, and, now, anyone who arranges words, letters, numbers, and symbols for publication, display, or distribution.</h3><h5> </h5>';
  442. 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.';
  443. const $specimen = $('<div class="specimen" contenteditable="true">'+ $sample_string +'</div><div class="hamburger" contenteditable="true">'+ $hamburger_string +'</div><div class="lorem first" style="font-size:1em;" contenteditable="true">'+ $lorem_string +'</div><div class="lorem" style="font-size:1em;" contenteditable="true">'+ $lorem_string +'</div><div class="lorem" style="font-size:1em;" contenteditable="true">'+ $lorem_string +'</div>');
  444. const $content_font = $('<div id="content_font" class="content" spellcheck="false" data-kind="font"></div>');
  445. const $content_image = $('<img id="content_image" class="content" data-kind="image" />');
  446. const $content_video = $('<video id="content_video" class="content media" controls data-kind="video">Your browser does not support the video tag.</video>');
  447. const $content_pdf = $('<embed id="content_pdf" class="content" name="plugin" tabindex="0" data-kind="pdf"></embed>');
  448. const $content_iframe = $('<iframe id="content_iframe" class="content" name="content_iframe" sandbox="allow-scripts allow-same-origin allow-modals" tabindex="0" data-kind="file"></iframe>');
  449. const $checkbox_cont = $('<div id="checkbox_div"></div>');
  450. const $title_buttons_left = $('<td id="title_buttons_left"><button id="reload_btn" tabindex="-1">Reload</button><button id="prev_next_btns" class="split_btn"><span id="prev_btn">&nbsp;</span><span id="next_btn">&nbsp;</span></button></td>');
  451. const $title = $('<td id="title"></td>');
  452. const $content_title = $('<tr id="content_title"></tr>');
  453. const $title_buttons_right = $('<td id="title_buttons_right"><button id="scale" class="split_btn"><span id="decrease">&nbsp;</span><span id="increase">&nbsp;</span></button><button id="close_btn" tabindex="-1">Close</button></td>');
  454. const $content_header = $('<header id="content_header"><table><tbody></tbody></table></header>');
  455. const $content_container = $('<section id="content_container"></section>');
  456. const $content_pane = $('<td id="content_pane" class=""></td>');
  457.  
  458. const $main_content = $('<table id="main_content"><thead></thead><tbody><tr></tr></tbody><tfoot></tfoot></table>');
  459.  
  460. // SVG UI ICONS
  461. 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\' ';
  462. 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>")';
  463. 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>")';
  464. const $svg_arrow = $svg_prefix + 'width=\'88.4px\' height=\'141.4px\' viewBox=\'0 0 88.4 141.4\' enable-background=\'new 0 0 88.4 141.4\' xml:space=\'preserve\'><polygon fill=\'%23231F20\' points=\'70.7,0 0,70.7 70.7,141.4 88.4,123.7 35.4,70.7 88.4,17.7 \'/></svg>")';
  465. 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>")';
  466. 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>")';
  467. 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>")';
  468. 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>")';
  469. 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>")';
  470. 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>")';
  471. 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>")';
  472. const $grid_icon_inv = $svg_prefix + 'width=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' enable-background=\'new 0 0 16 16\' xml:space=\'preserve\'><g><path fill=\'%23CCCCCC\' d=\'M5,2v3H2V2H5 M7,0H0v7h7V0L7,0z\'/></g><g><path fill=\'%23CCCCCC\' d=\'M14,2v3h-3V2H14 M16,0H9v7h7V0L16,0z\'/></g><g><path fill=\'%23CCCCCC\' d=\'M5,11v3H2v-3H5 M7,9H0v7h7V9L7,9z\'/></g><g><path fill=\'%23CCCCCC\' d=\'M14,11v3h-3v-3H14 M16,9H9v7h7V9L16,9z\'/></g></svg>")';
  473. 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>")';
  474. 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>")';
  475. 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>")';
  476. 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>")';
  477. 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>")';
  478. 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>")';
  479. const $error_icon = $svg_prefix + 'viewBox=\'0 0 512 512\' enable-background=\'new 0 0 512 512\' xml:space=\'preserve\'><g id=\'Layer_2\'> <path fill=\'%23FFB636\' fill-opacity=\'0.75\' d=\'M12.51,470.379L234.371,16.008c6.439-13.187,25.17-13.363,31.855-0.299l232.51,454.371 c6.064,11.849-2.542,25.92-15.853,25.92H28.512C15.348,496,6.734,482.209,12.51,470.379z\'/></g><g id=\'Layer_3\'> <path fill=\'%23444444\' fill-opacity=\'0.75\' d=\'M284.332,173L272.15,336.498c-0.911,12.233-11.567,21.411-23.8,20.499 c-11.116-0.828-19.706-9.707-20.499-20.499L215.668,173c-1.413-18.961,12.813-35.478,31.774-36.89s35.478,12.813,36.89,31.774 C284.456,169.546,284.441,171.384,284.332,173z M250,391.873c-17.432,0-31.564,14.131-31.564,31.564 C218.436,440.869,232.568,455,250,455s31.564-14.131,31.564-31.564C281.564,406.004,267.432,391.873,250,391.873z\'/></g></svg>")';
  480. const $bookmark_icon = $svg_prefix + 'width=\'20px\' height=\'20px\' viewBox=\'0 0 20 20\' enable-background=\'new 0 0 20 20\' xml:space=\'preserve\'><title>bookmark outlined</title><path fill=\'%23555555\' d=\'M3,1v18l7-5l7,5V1H3z M10,11.14l-4.5,3.15V3.5h9v10.79L10,11.14z\'/></svg>")';
  481. const $bookmark_icon_dark = $svg_prefix + 'width=\'20px\' height=\'20px\' viewBox=\'0 0 20 20\' enable-background=\'new 0 0 20 20\' xml:space=\'preserve\'><title>bookmark outlined</title><path fill=\'%23AAAAAA\' d=\'M3,1v18l7-5l7,5V1H3z M10,11.14l-4.5,3.15V3.5h9v10.79L10,11.14z\'/></svg>")';
  482. // const $spinner = $svg_prefix + ' width=\'40px\' height=\'40px\' viewBox=\'-1 -1 40 40\' style=\'enable-background:new -1 -1 40 40;\' xml:space=\'preserve\'><g> <g transform=\'translate(1 1)\'> <circle style=\'fill:none;stroke:%23666666;stroke-width:4;stroke-opacity:0.5;\' cx=\'18\' cy=\'18\' r=\'18\'/> <path style=\'fill:none;stroke:%23AAAAAA;stroke-width:4;\' d=\'M36,18c0-9.94-8.061-18-18-18 \'><animateTransform type=\'rotate\' fill=\'remove\' repeatCount=\'indefinite\' attributeName=\'transform\' restart=\'always\' dur=\'1s\' from=\'0 18 18\' calcMode=\'linear\' to=\'360 18 18\' accumulate=\'none\' additive=\'replace\'> </animateTransform> </path> </g></g></svg>")';
  483. //SVG FILE ICONS
  484. // Chrome default icons
  485. 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= ")';
  486. const $file_icon_file_default = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
  487. // Custom file icons
  488. 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\'> ';
  489. 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> ")';
  490. 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> ")';
  491. 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> ")';
  492. 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> ")';
  493. 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> ")';
  494. 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> ")';
  495. 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> ")';
  496. 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>")';
  497. 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>")';
  498. 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>")';
  499. 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>")';
  500. 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>")';
  501. 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>")';
  502. const $file_icon_video = $svg_icon_prefix + '<path style=\'fill:%234D4D4D;\' d=\'M14,0v14h-0.94v-0.83h-1.68V14H2.62v-0.83H0.94V14H0V0h0.94v0.87h1.68V0h8.76v0.87h1.68V0H14z M10.5,5.15h-7v3.7h7V5.15z M10.5,0.71h-7v3.7h7V0.71z M10.5,9.59h-7v3.7h7V9.59z\'/></svg>")';
  503. const $file_icon_audio = $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=\'1\'> <path fill=\'%23008888\' 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>")';
  504. // 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>")';
  505.  
  506. // Assemble UI Elements
  507. function assembleUIElements() {
  508. $parents_dir_menu.find('div').append( $current_dir_path );
  509. $sidebar_header.find('thead th');
  510. $sidebar_header.find('tbody tr').first().find('td').first().append( $parent_dir_menu ).next().append( $parents_dir_menu ).next().append( $shortcuts_menu );
  511. $sidebar_header.find('tbody tr:last-child td').append( $show_details, $inv_checkbox, $grid_btn );
  512. $dir_list_head.append($text_editor_item);
  513. $dir_list.append($dir_list_head, $dir_list_body, $tfoot);
  514. $sidebar.append($sidebar_header, $dir_list);
  515. $sidebar_wrapper.append( $sidebar, $handle, $toggle_sidebar );
  516. // content pane items
  517. $checkbox_cont.append( $loop, $shuffle );
  518. $content_audio.find('td').append( $prev_track, $next_track, $audio_player, $close_audio, $checkbox_cont );
  519. $content_title.append($title_buttons_left, $title, $title_buttons_right);
  520. $content_header.find('tbody').append( $content_title, $content_audio_title, $content_audio );
  521. $content_font.append( $specimen );
  522. $content_container.append( $content_grid, $content_text, $content_font, $content_image, $content_pdf, $content_video, $content_iframe );
  523. $content_pane.append( $content_header, $content_container );
  524. $main_content.find('thead').append($warnings, $overlay);
  525. }
  526.  
  527. //***** STYLES *****//
  528.  
  529. // DEFINE STYLES
  530. var $main_style_rules =
  531. // Align-content
  532. '#content_pane.has_image #content_container' + //body.theme_light #dir_list .selected.audio,
  533. '{ align-items: center; justify-content: center; }' +
  534. // BACKGROUND: COLOR
  535. '#sidebar_wrapper, body.theme_light #sidebar, body.theme_light #sidebar ul, body.theme_light #preview_text, body.theme_light #source_text, body.theme_light #content_header' +
  536. '{ background-color: lightgray }' +
  537. 'body.theme_light #dir_list tr.selected:not(.playing)' + //body.theme_light #dir_list .selected.audio,
  538. '{ background-color: lightsteelblue !important; }' +
  539. 'body.theme_light #dir_list tr.loaded:not(.selected)' + //body.theme_light #dir_list .selected.audio,
  540. '{ background-color: rgba(69%,77%,87%,0.75); }' +
  541. 'body.theme_light #dir_list tr.loaded:hover' + //body.theme_light #dir_list .selected.audio,
  542. '{ background-color: rgba(69%,77%,87%,1) !important; }' +
  543. '#content_pane.has_grid .image_grid_item::after' + //body.theme_light #dir_list .selected.audio,
  544. '{ background-color: gray; }' +
  545. 'body.theme_dark #dir_list tr.selected:not(.playing)' +
  546. '{ background-color: slategray !important; }' +
  547. 'body.theme_dark #dir_list tr.loaded:not(.selected)' +
  548. '{ background-color: rgba(44%,50%,56%,0.5); }' +
  549. 'body.theme_dark #dir_list tr.loaded:hover' +
  550. '{ background-color: rgba(44%,50%,56%,1) !important; }' +
  551. 'body.theme_dark #content_pane.has_grid #content_container, body.theme_dark #content_grid > div:hover, body.theme_dark #content_grid > div.hovered, body.theme_dark #content_grid > div.selected' +
  552. '{ background-color: #262626; }' +
  553. 'body.theme_dark #content_grid > div, body.theme_dark #content_pane.has_image, body.theme_dark #content_font, body.theme_dark #content_pane, body.theme_dark #content_pane.has_grid #content_grid::after, .split_btn::after' +
  554. '{ background-color: #333; }' +
  555. 'body.theme_dark #sidebar ul, body.theme_dark #preview_text_menu_item span, body.theme_dark #sidebar_header thead, body.theme_dark #sidebar_menus, body.theme_dark #preview_text, body.theme_dark #source_text, body.theme_dark.alternate_background #dir_list tbody tr:nth-of-type(odd):not(.selected):not(.loaded), body.theme_dark.alternate_background #dir_list tbody tr:nth-of-type(odd):not(.loaded), body.theme_dark #tfoot, body.theme_dark .playlist_item:nth-of-type(odd)' +
  556. '{ background-color: #444; }' +
  557. 'body.theme_dark #sidebar, body.theme_dark #dir_list #tbody, body.theme_dark #grid_btn .menu li, body.theme_dark #content_header, body.theme_dark .font_grid_item:hover, body.theme_dark .font_grid_item.hovered, body.theme_dark .image_grid_item:hover, body.theme_dark .image_grid_item.hovered' +
  558. '{ background-color: #555; }' +
  559. 'body.theme_dark #sidebar ul li:not(#preview_text_menu_item):hover, body.theme_dark #preview_text_menu_item span:hover, body.theme_dark #preview_text:hover, body.theme_dark #source_text:hover' +
  560. '{ background-color: #666; }' +
  561. 'body.theme_dark #sidebar .hovered, body.theme_dark #grid_btn .menu li:hover, body.theme_dark #dir_list tbody tr:hover, body.theme_dark #dir_list tbody li:hover, body.theme_dark.alternate_background #dir_list #tbody tr:hover, body.theme_dark.alternate_background #dir_list #tbody tr:nth-of-type(odd).hovered, body.theme_dark #content_iframe[href^="/"]' +
  562. '{ background-color: #777; }' +
  563. 'body.theme_light #sidebar_header thead tr, body.theme_light #sidebar_menus, #parents_dir_menu + ul li:hover, #shortcuts_menu + ul li:not(.has_submenu):not(#preview_text_menu_item):hover, body.theme_light #preview_text_menu_item span:hover, body.theme_light #preview_text:hover, body.theme_light #source_text:hover, #grid_btn.has_images.has_fonts:hover .menu li:hover, body.theme_light #dir_list tbody tr:hover, body.theme_light.alternate_background #dir_list #tbody tr:hover, body.theme_light #dir_list .hovered, body.theme_light.alternate_background #dir_list #tbody tr.hovered' +
  564. '{ background-color: #BBB; }' +
  565. 'body.theme_light #grid_btn .menu li, body.theme_light.alternate_background #dir_list tbody tr:nth-of-type(odd):not(.selected):not(.playing):not(.loaded), body.theme_light #tfoot, body.theme_light .playlist_item:nth-of-type(odd)' +
  566. '{ background-color: #CCC; }' +
  567. 'body.theme_light #dir_list tbody' +
  568. '{ background-color: #DFDFDF; }' +
  569. 'body.theme_light #content_pane.has_image, body.theme_light #content_grid > div, #warnings, body.theme_light #content_pane.has_grid #content_grid::after, body.theme_light #content_pane.has_grid .image_grid_item::after' +
  570. '{ background-color: #EEE; }' +
  571. 'body.theme_light #content_pane, body.theme_light #content_grid, body.theme_light #content_grid div.selected, #content_font, #content_iframe, #scale, body.is_chrome #audio:focus, body.theme_light #content_grid div:hover, body.theme_light #content_grid div.hovered' +
  572. '{ background-color: #FFF; }' +
  573. // EXCLUDED: Prevent previewed files with these extensions from being inverted in dark mode
  574. //for ( i = 0; i < $row_settings.exclude.length; i += 1) {
  575. // 'body.theme_dark #content_iframe[src*="'+ $row_settings.exclude[i] +'" i] { background:#FFF; filter: unset; }' +
  576. //}
  577. 'body.is_chrome #audio' +
  578. '{ background-color: rgb(241, 243, 244); }' +
  579. 'body.theme_light #dir_list .playing' +
  580. '{ background-color: #99BEBF; }' +
  581. 'body.theme_dark #dir_list .playing, body.theme_dark.alternate_background #dir_list tbody tr.playing:nth-of-type(odd)' + //body.theme_dark #dir_list .selected.audio,
  582. '{ background-color: #4C7E80; }' +
  583. 'body.theme_light.has_text #text_editor_row, body.theme_light.edited #text_editor_row' +
  584. '{ background-color: #B0B3DA; }' +
  585. 'body.theme_dark.has_text #text_editor_row, body.theme_dark.edited #text_editor_row' +
  586. '{ background-color: #606490; }' +
  587. '#dir_list tbody tr, #dir_list .audio a, #dir_list .video a, body.has_audio #content_pane.has_ignored, 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' +
  588. '{ background-color: transparent; }' +
  589. '#warnings button:focus,#warnings button.focus' +
  590. '{ background: #0E4399; }' +
  591.  
  592. // BACKGROUND: SVG IMAGES & ICONS
  593. '#parent_dir_menu a { background:' + $up_arrow + ' center no-repeat; }' +
  594. '#shortcuts_menu div { background:'+ $menu_icon + ' center no-repeat; }' +
  595. 'body.theme_light .bookmarks > a { background:'+ $bookmark_icon +' 4px no-repeat; background-size: 12px; }' +
  596. 'body.theme_dark .bookmarks > a { background:'+ $bookmark_icon_dark +' 4px no-repeat; background-size: 12px; }' +
  597. 'body.theme_light #shortcuts > li.has_submenu { background:'+ $menu_arrow +' right no-repeat; background-size: 12px; }' +
  598. 'body.theme_light #shortcuts > li.has_submenu:hover { background:#BBB '+ $menu_arrow +' right no-repeat; background-size: 12px; }' +
  599. 'body.theme_dark #shortcuts > li.has_submenu { background:'+ $menu_arrow_inv +' right no-repeat; background-size: 12px; }' +
  600. 'body.theme_dark #shortcuts > li.has_submenu:hover { background:#666 '+ $menu_arrow_inv +' right no-repeat; background-size: 12px; }' +
  601. 'body.theme_light.sort_by_default #sort_by_default span::before, body.theme_light.sort_by_name #sort_by_name span::before, body.theme_light.sort_by_size #sort_by_size span::before, body.theme_light.sort_by_date #sort_by_date span::before, body.theme_light.sort_by_kind #sort_by_kind span::before, body.theme_light.sort_by_ext #sort_by_ext span::before, body.theme_light.sort_by_default #default, body.theme_light.sort_by_name #name, body.theme_light.sort_by_size #size, body.theme_light.sort_by_date #date, body.theme_light.sort_by_kind #kind, body.theme_light.sort_by_ext #ext, body.theme_light #theme span, body.theme_light.alternate_background #alternate_background span, body.theme_light.show_numbers #show_numbers span, body.theme_light.autoload_media #autoload_media span, body.theme_light.split_view #split_view_menu_item, body.theme_light.split_view #split_view, body.theme_light:not(.enable_text_editing) #enable_text_editing, body.theme_light.source_text #source_text, body.theme_light.preview_text #preview_text { background:'+ $check_mark +' 5px center no-repeat; background-size:10px; }' +
  602. 'body.theme_dark.sort_by_default #sort_by_default span::before, body.theme_dark.sort_by_name #sort_by_name span::before, body.theme_dark.sort_by_size #sort_by_size span::before, body.theme_dark.sort_by_date #sort_by_date span::before, body.theme_dark.sort_by_kind #sort_by_kind span::before, body.theme_dark.sort_by_ext #sort_by_ext span::before, body.theme_dark.sort_by_default #default, body.theme_dark.sort_by_name #name, body.theme_dark.sort_by_size #size, body.theme_dark.sort_by_date #date, body.theme_dark.sort_by_kind #kind, body.theme_dark.sort_by_ext #ext, body.theme_dark #theme span, body.theme_dark.alternate_background #alternate_background span, body.theme_dark.show_numbers #show_numbers span, body.theme_dark.autoload_media #autoload_media span, body.theme_dark.split_view #split_view_menu_item, body.theme_dark.split_view #split_view, body.theme_dark:not(.enable_text_editing) #enable_text_editing, body.theme_dark.source_text #source_text, body.theme_dark.preview_text #preview_text { background:'+ $check_mark_inv +' 5px center no-repeat; background-size:10px; }' +
  603. 'body #grid_btn { background:'+ $grid_icon +' right 6px top 0 no-repeat; }' +
  604. 'body.theme_light.has_images.has_fonts #grid_btn:hover .menu { background: #DDD '+ $grid_icon +' right 6px top 6px no-repeat; }' +
  605. 'body.theme_dark.has_images.has_fonts #grid_btn:hover .menu { background: #555 '+ $grid_icon_inv +' right 6px top 6px no-repeat; }' +
  606. '#toggle_sidebar, #toggle_info { background:'+ $toggle +' center no-repeat; background-size:12px; }' +
  607. // 'body.theme_light #dir_list thead th.checked span::before { background-image:'+ $check_mark +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  608. // 'body.theme_dark #dir_list thead th.checked span::before { background-image:'+ $check_mark_inv +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  609. 'body.theme_light.sort_by_default #sort_by_default span::after, body.theme_light.sort_by_name #sort_by_name span::after, body.theme_light.sort_by_size #sort_by_size span::after, body.theme_light.sort_by_date #sort_by_date span::after, body.theme_light.sort_by_kind #sort_by_kind span::after, body.theme_light.sort_by_ext #sort_by_ext span::after { background-image:'+ $up_arrow +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  610. 'body.theme_dark.sort_by_default #sort_by_default span::after, body.theme_dark.sort_by_name #sort_by_name span::after, body.theme_dark.sort_by_size #sort_by_size span::after, body.theme_dark.sort_by_date #sort_by_date span::after, body.theme_dark.sort_by_kind #sort_by_kind span::after, body.theme_dark.sort_by_ext #sort_by_ext span::after { background-image:'+ $up_arrow_inv +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  611. '#dir_list tbody a { background-size:auto 13px; background-position:6px 4px; }' +
  612. 'body.has_audio #content_pane:not(.has_image):not(.has_video):not(.has_ignored):not(.has_grid) { background-image: ' + $music +'; background-position: center; background-repeat: no-repeat; background-size:33.33%; }' +
  613. '#prev_track, #next_track { background: rgb(241, 243, 244) '+ $next_track_arrow +' center no-repeat; background-blend-mode:difference; }' +
  614. '#prev_track:hover, #next_track:hover, #close_audio:hover { background-blend-mode:normal; }' +
  615. '#close_audio { background:rgb(241, 243, 244); }' +
  616. '#close_audio::after { background:'+ $plus_sign +' center no-repeat; background-blend-mode:difference; background-size:14px; }' +
  617. '#decrease { background:'+ $minus_sign +' center no-repeat; background-size: 10px; }' +
  618. '#increase { background:'+ $plus_sign +' center no-repeat; background-size: 10px; }' +
  619. '#prev_btn, #next_btn { background: ' + $svg_arrow + ' 45% center no-repeat; background-size: contain; }' +
  620. 'body #content_pane.has_ignored:not(.has_grid) { background-image:'+ $file_icon_ignored +'; background-position: center; background-repeat: no-repeat; background-size:50%; }' +
  621. 'body.theme_dark #content_pane.has_ignored:not(.has_grid) { background-color:#333; background-image:'+ $file_icon_ignored_inv +'; background-position: center; background-repeat: no-repeat; background-size:50%; }' +
  622. 'body.theme_light.is_error #content_pane { background:#FFF '+ $error_icon +' center no-repeat; background-size:50%;}' +
  623. 'body.theme_dark.is_error #content_pane { background:#333 '+ $error_icon +' center no-repeat; background-size:50%;}' +
  624. // '#content_pane.has_image #content_container, #content_pane.has_pdf #content_container { background: '+ $spinner +' center no-repeat; background-size:20px; }' +
  625. // Default File Icons
  626. 'body.use_default_icons #dir_list .dir a.icon span { background:'+ $file_icon_dir_default + ' 6px 0 no-repeat; background-size:auto 13px; }' +
  627. 'body.use_default_icons #dir_list .file a.icon span { background:'+ $file_icon_file_default + ' 6px 0 no-repeat; background-size:auto 13px; }' +
  628. // Custom File Icons
  629. 'body.use_custom_icons #dir_list tr.file:not(.dir) a.icon span { background: '+ $file_icon_file +' 6px 0 no-repeat; background-size:14px 14px; }' +
  630. 'body.use_custom_icons #dir_list tr.file.audio a.icon span, body.use_custom_icons #dir_list tr.file.video a.icon span { background: transparent; }' +
  631. 'body.use_custom_icons #dir_list tr.file.font a.icon span { background: '+ $file_icon_font +' 6px 0 no-repeat; background-size:14px 14px; }' +
  632. 'body.use_custom_icons #dir_list tr.file.image a.icon span { background: '+ $file_icon_image +' 6px 0 no-repeat; background-size:14px 14px; }' +
  633. 'body.use_custom_icons #dir_list tr.file.pdf a.icon span { background: '+ $file_icon_pdf +' 6px 0 no-repeat; background-size:14px 14px; }' +
  634. 'body.use_custom_icons #dir_list tr.file[class*="htm"] a.icon span { background: '+ $file_icon_html +' 6px 0 no-repeat; background-size:14px 14px; }' +
  635. 'body.use_custom_icons #dir_list tr.file.ignore a.icon span { background: '+ $file_icon_ignored +' 6px 0 no-repeat; }' +
  636. 'body.use_custom_icons #dir_list tr.file.invisible a.icon span { background: '+ $file_icon_invisible +' 6px 0 no-repeat; background-size:14px 14px; }' +
  637. 'body.use_custom_icons #dir_list tr.dir.invisible a.icon span { background: '+ $file_icon_dir_invisible +' 6px 0 no-repeat; background-size:14px 14px; }' +
  638. 'body.use_custom_icons #dir_list tr.dir:not(.app) a.icon span { background: '+ $file_icon_dir +' 6px 0 no-repeat; background-size:14px 14px; }' +
  639. 'body.use_custom_icons #dir_list tr.app a span { background: '+ $file_icon_app +' 6px 0 no-repeat !important; background-size:14px 14px; }' +
  640. 'body.use_custom_icons #dir_list tr.file.code a.icon span { background: '+ $file_icon_code +' 6px 0 no-repeat; background-size:14px 14px }' +
  641. 'body.use_custom_icons #dir_list tr.file.text a.icon span, body.use_custom_icons #dir_list tr.file.markdown a.icon span { background: '+ $file_icon_text +' 6px 0 no-repeat; background-size:14px 14px }' +
  642. // shortcuts icons
  643. '#shortcuts ul a { background: '+ $file_icon_file +' 8px center no-repeat; background-size:14px 14px }' +
  644. '#shortcuts ul a[href^="file"] { background: '+ $file_icon_dir +' 8px center no-repeat; background-size:14px 14px }' +
  645. '#shortcuts ul a[href^="http"] { background: '+ $file_icon_html +' 8px center no-repeat; background-size:14px 14px }' +
  646. // BORDERS
  647. ':root, html, body, #sidebar_wrapper, #sidebar_header, #dir_list, #main_content, #content_pane, #content_pdf, #content_iframe' +
  648. '{ border: 0 !important; }' +
  649. 'body.theme_dark ul' +
  650. '{ border: solid 1px #222; }' +
  651. 'button' +
  652. '{ border: solid 1px #333; }' +
  653. 'body.theme_light #sidebar ul' +
  654. '{ border: solid 1px gray; }' +
  655. // border-top
  656. 'body.theme_dark #sidebar_header .menu, body.theme_dark #grid_btn ul.menu, body.theme_dark #text_editor_row, body.theme_dark:not(.is_error) #dir_list #tbody, body.theme_dark #tfoot' +
  657. '{ border-top:solid 1px #111; }' +
  658. 'body.theme_light #dir_list tbody, #grid_btn .menu, body.theme_light #shortcuts, body.theme_light #text_editor_row, #tfoot, .font_grid_item:not(:first-of-type), .image_grid_item + .font_grid_item' +
  659. '{ border-top:solid 1px grey; }' +
  660. '#sort_by, tr.sorted, tr:not(.invisible) ~ tr.invisible + tr:not(.invisible)' + // , tr.dir ~ tr:not(.dir,.invisible,.ignore)
  661. '{ border-top:solid 1px #999; }' +
  662. // border-right
  663. '#parents_dir_menu + ul, #shortcuts_menu + ul, #grid_btn ul.menu' +
  664. '{ border-right:0 !important; }' +
  665. 'body.theme_dark #parents_dir_menu, body.theme_dark #sidebar, body.theme_dark #grid_btn .menu li, body.theme_dark #warnings' +
  666. '{ border-right:solid 1px #111; }' +
  667. '#sidebar, body.theme_light #parents_dir_menu, #grid_btn .menu li, .image_grid_item' +
  668. '{ border-right:solid 1px grey; }' +
  669. // border-bottom
  670. 'body.theme_dark #sidebar_title, body.theme_dark #sidebar_menus, body.theme_dark #sidebar_header .menu, body.theme_dark #content_header, body.theme_dark #content_pane.has_audio #content_title, body.theme_dark #grid_btn .menu li:first-of-type, body.theme_dark #grid_btn ul.menu, body.theme_dark #warnings' +
  671. '{ border-bottom:solid 1px #111; }' +
  672. 'body.theme_light #sidebar_header thead tr, body.theme_light #shortcuts, body.theme_light #sidebar_menus, #grid_btn .menu li:first-of-type, #grid_btn .menu, .specimen, .font_grid_item:last-of-type, .image_grid_item' +
  673. '{ border-bottom:solid 1px grey; }' +
  674. 'body.theme_light #content_title, body.theme_light #content_pane.has_audio #content_header' +
  675. '{ border-bottom:solid 1px #888; }' +
  676. '#shortcuts_menu + ul > li.has_submenu.ruled, .rule' +
  677. '{ border-bottom:solid 1px #999; }' +
  678. 'body.theme_dark #content_pane.has_font #content_font hr, .hamburger h4' +
  679. '{ border-bottom:solid 1px #CCC; }' +
  680. // border-left
  681. '#parents_dir_menu + ul, #shortcuts_menu + ul' +
  682. '{ border-left:0; }' +
  683. 'body.theme_dark #parents_dir_menu, body.theme_dark #grid_btn ul.menu, body.theme_dark #warnings' +
  684. '{ border-left:solid 1px #111; }' +
  685. 'body.theme_light #parents_dir_menu, #grid_btn .menu' +
  686. '{ border-left:solid 1px grey; }' +
  687. // border-collapse
  688. 'table' +
  689. '{ border-collapse: collapse; }' +
  690. // border-radius
  691. 'html, body, :root' +
  692. '{ border-radius: 0; }' +
  693. 'button' +
  694. '{ border-radius: 3px; }' +
  695. '#warnings' +
  696. '{ border-radius: 0 0 3px 3px; }' +
  697. // bottom
  698. '#handle, #parent_dir_menu, #dir_list, #tfoot, #close_audio::after, #dir_list tbody, #content_container, #overlay, #content_pane.has_grid #content_grid::after, #content_pane.has_grid .image_grid_item::after, .split_btn::after' +
  699. '{ bottom: 0; }' +
  700. // box-shadow
  701. 'body.theme_light #sidebar ul' +
  702. '{ box-shadow: 0px 2px 3px -2px #888; }' +
  703. 'body.theme_dark #sidebar ul' +
  704. '{ box-shadow:0px 2px 3px -2px #111; }' +
  705. '#warnings' +
  706. '{ box-shadow:0px 2px 12px 0 #111; }' +
  707. 'body.theme_dark #grid_btn ul.menu' +
  708. '{ box-shadow:none; }' +
  709. // box-sizing
  710. 'html, body, :root, #sidebar, #grid_btn .menu li, #content_container, #content_source, #content_preview' +
  711. '{ box-sizing: border-box; }' +
  712. // clear
  713. '#dir_list tbody .name' +
  714. '{ clear:right; }' +
  715. '#checkbox_div label, #grid_btn .menu li, #content_grid .font_grid_item' +
  716. '{ clear: both; }' +
  717. // columns
  718. '.lorem + .lorem' +
  719. '{ columns:2; column-gap:2em; }' +
  720. '.lorem + .lorem + .lorem' +
  721. '{ columns:3; }' +
  722. // COLOR
  723. 'body.theme_light #sidebar, body.theme_light #sidebar a, #content_header, body.theme_light #content_font, body.theme_light .font_grid_item a, body.theme_light .image_grid_item p, body.theme_light #content_font, body.theme_light .font_grid_item p, body.theme_light .font_grid_item a, body.theme_light .image_grid_item p, .split_btn span, body #warnings' +
  724. '{ color: #111 }' +
  725. 'body.theme_light #sidebar #tfoot tr' +
  726. '{ color: #444 }' +
  727. 'body.theme_light #dir_list tr.ignore a, body.theme_light #dir_list tr.ignore.app a' +
  728. '{ color: #777 }' +
  729. '#sort_menu .disabled span, #dir_list th.disabled' +
  730. '{ color: #999 }' +
  731. 'body.theme_dark #dir_list tr.ignore td, body.theme_dark #dir_list tr.file.ignore a, body.theme_dark #sidebar #tfoot td' +
  732. '{ color: #BBB }' +
  733. 'body.theme_dark #sidebar th:not(.disabled), body.theme_dark #sidebar td, body.theme_dark #sidebar a, body.theme_dark #error_message *, body.theme_dark #content_header tr, body.theme_dark #content_header tr a, body.theme_dark #content_font, body.theme_dark .font_grid_item p, body.theme_dark .font_grid_item a, body.theme_dark .image_grid_item p, #warnings button:focus, #warnings button.focus' +
  734. '{ color: #EEE }' +
  735. // CONTENT
  736. 'body#top.edited #warnings.unloading h3::before, #dir_list th span::before, #dir_list th span::after, body.has_hidden_sidebar #handle, #dir_list tr:empty, #dir_list .audio td.icon, #dir_list .video td.icon, tr.invisible, body.hide_ignored .ignore, .split_btn::after, #close_audio::after, #content_pane.has_grid #content_grid::after, #content_pane.has_grid .image_grid_item::after' +
  737. '{ content:"" }' +
  738. '#content_pane.has_audio #content_audio_title td::before, body #content_pane.has_video #title::before' +
  739. '{ content:"Playing: " }' + // .audio, .video
  740. '#content_pane.has_dir:not(.has_grid) #title::before' +
  741. '{ content:"Index of: " }' + // .dir
  742. '#content_pane.has_grid:not(.has_ignored) #title::before' +
  743. '{ content:"Fonts and Images from: " }' + // font and image grid
  744. 'body:not(.has_images) #content_pane.has_grid:not(.has_ignored) #title::before, #content_pane.has_grid #title.font_grid::before' +
  745. '{ content:"Fonts from: " }' + // font grid
  746. 'body:not(.has_fonts) #content_pane.has_grid:not(.has_ignored) #title::before, #content_pane.has_grid #title.image_grid::before' +
  747. '{ content:"Images from: " }' + // image grid
  748. '#content_pane.has_ignored:not(.has_grid) #title::before' +
  749. '{ content:"Ignored content: " }' + // .ignored
  750. 'body.edited.has_text #title::after, body.iframe_edited:not(.has_text) #content_pane.has_iframe #title::after' +
  751. '{ content:" (edited)" }' + // .ignored
  752. 'body#top.edited #text_editor_row a:after' +
  753. '{ content: " (edited)" }' + // .dir
  754. 'body#top.edited #warnings.unloading h3::before' +
  755. '{ content: "Text Editor: " }' + // .dir
  756. '#title::after' +
  757. '{ content: attr(data-after) }' + // .dir
  758. // counter
  759. 'body.show_numbers #tbody' +
  760. '{ counter-reset: row; }' +
  761. 'body.show_numbers #tbody tr td.name a::before' +
  762. '{ counter-increment: row; content:counter(row);}' +
  763. // cursor
  764. '#sidebar_title th, #sort_menu .disabled span, #dir_list .disabled' +
  765. '{ cursor: default; }' +
  766. '#sidebar_menus td:hover, #parents_dir_menu div, #shortcuts_menu div, #grid_btn, #dir_list thead th, #toggle_info, .split_btn span' +
  767. '{ cursor: pointer; }' +
  768. '#handle' +
  769. '{ cursor: col-resize; }' +
  770. '#content_pane:not(.has_zoom_image) #content_image' +
  771. '{ cursor: zoom-in; }' +
  772. '#content_pane.has_zoom_image content_image' +
  773. '{ cursor: zoom-out; }' +
  774. // DISPLAY
  775. '#sidebar_menus ul, #show_details span, #grid_btn, #grid_btn .menu, #dir_list thead .name input, #dir_list thead th.up::before, #dir_list .details, #dir_list thead td.icon, #dir_list tr::before, #tbody tr td:not(.name), #text_editor_row, body.has_hidden_sidebar #handle, body.has_hidden_sidebar #sidebar, #dir_list tr:empty, #dir_list tr.audio td.icon, #dir_list tr.video td.icon, tr.invisible, body.hide_ignored #tbody tr.ignore, #content_title::before, #content_pane #content_audio_title, .split_btn, #content_pane #content_audio, #content_pane #content_grid, #content_pane #content_text, #content_pane .content, #content_pane.has_grid .content, body.has_text .content:not(#content_text), #content_pane.has_hidden_grid #content_grid, body.theme_light #theme_dark, body.theme_dark #theme_light, body.use_custom_icons #dir_list img, #dir_list tr.file.audio a img, #dir_list tr.file.video a img, #warnings, #warnings p, #warnings button, #clear_warning, #overlay' +
  776. '{ display: none; }' +
  777. 'body.is_error #sidebar_buttons, body.is_error #thead, body.is_error #text_editor_row, body.is_error #content_pane.has_iframe #content_iframe.has_content' +
  778. '{ display: none !important; }' +
  779. '#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, body.show_details #show_details span:last-of-type, body:not(.show_details) #show_details span:first-of-type, body.has_hidden_text #text_editor_row, #sort_by_default, #dir_list tbody a, #dir_list tfoot tr, #content_header, #content_pane.has_font:not(.has_grid) #content_font, #content_pane.has_image:not(.has_grid) #content_image, #content_pane.has_zoom_image #content_image, #content_pane.has_pdf:not(.has_grid) #content_pdf, body.has_text #content_pane:not(.has_grid) #content_text, #content_pane.has_video:not(.has_grid) #content_video, body:not(.has_text) #content_pane.has_iframe:not(.has_grid) #content_iframe, #content_pane.has_dir:not(.has_grid) #content_iframe, #content_pane.has_grid #content_grid.has_grid, #content_pane.has_grid #content_grid.has_font_grid, #content_pane.has_grid .nav_btn, #content_pane.has_font .nav_btn, .font_grid_item a, .image_grid_item a, #checkbox_div label, body.theme_dark #theme_dark, body.theme_light #theme_light, body:not(.has_hidden_sidebar) #dir_list .name, body.has_warning #warnings, body.has_warning #warnings.unloading p#warning_unsaved, body.has_warning #warnings.clear #warning_clear, body.has_warning #warnings.local p#warning_local, body.has_warning #overlay' +
  780. '{ display: block; }' +
  781. 'body.has_text #dir_list tr#text_editor_row td, body #dir_list tr#text_editor_row td a, body.show_details #text_editor_row td, body.show_details #text_editor_row td a, body.has_images.has_fonts #grid_btn:hover ul.menu' +
  782. '{ display:block !important; }' +
  783. '#parents_dir_menu div, body.has_fonts #grid_btn, body.has_images #grid_btn, #dir_list thead th span::before, #enable_text_editing::before, #dir_list thead th span::after, body.show_details #dir_list th.details:not(#name):not(#sort_by_default), body.show_details #dir_list td.details:not(.name):not(.ext), body.show_numbers #tbody tr td.name a::before, body.has_audio #dir_list thead input, #title, #checkbox_div, #prev_track, #next_track, #close_audio, #content_pane.has_grid .split_btn, #content_pane.has_image .split_btn, #content_pane.has_zoom_image .split_btn, #content_pane.has_font .split_btn, .split_btn span, body.has_warning #warnings.unloading #warning_ignore_btn, body.has_warning #warnings.unloading #warning_cancel_btn, body.has_warning #warnings.unloading #warning_save_btn, body.has_warning #warnings.clear #warning_clear_btn, body.has_warning #warnings.clear #warning_cancel_btn, body.has_warning #warnings.local #warning_ok_btn' +
  784. '{ display: inline-block; }' +
  785. 'body:not(.is_gecko) #dir_list tr td.name span, #content_pane.has_image:not(.has_grid) #content_container, #content_pane.has_video:not(.has_grid) #content_container' +
  786. '{ display: flex; }' +
  787. 'body.is_gecko #dir_list tr td.name span' +
  788. '{ display: flow-root; }' +
  789. '#content_pane.has_audio #content_audio_title, #content_pane.has_audio #content_audio' +
  790. '{ display: table-row !important; }' +
  791. '#shortcuts_menu div, #content_pane, #content_pane.has_audio #content_audio td' +
  792. '{ display: table-cell; }' +
  793. // display grid: dir_list editor row
  794. 'body.has_text #dir_list #text_editor_row, body.show_details #text_editor_row' +
  795. '{ display: grid; grid-gap:0; grid-template-columns: 1fr; }' +
  796. // display grid: table header
  797. '#theader' +
  798. '{ display: grid; grid-gap:0; grid-template-columns: 1fr 1fr 1fr 1fr; }' +
  799. 'th#sort_by_name' +
  800. '{ grid-column: 1 / span 2; }' +
  801. 'th#sort_by_default' +
  802. '{ grid-column: 3 / span 2; }' +
  803. // display grid: dir_list rows and cells
  804. '#dir_list #tbody tr:not(.invisible), body.show_invisibles #tbody tr.invisible, body.show_details #dir_list tr td.details' +
  805. '{ display: grid; grid-gap:0; grid-template-columns: minmax(auto,6rem) 1fr minmax(auto,6rem); }' +
  806. 'td.name' +
  807. '{ grid-column: 1 / span 3; }' +
  808. 'td.size' +
  809. '{ grid-column: 1; grid-row: 2; }' +
  810. 'td.date' +
  811. '{ grid-column: 2; grid-row: 2; }' +
  812. 'td.kind' +
  813. '{ grid-column: 3; grid-row: 2; }' +
  814. // display grid: content pane grids
  815. '#content_pane.has_grid #content_grid' +
  816. '{ display: grid !important; grid-gap:0; grid-template-columns: repeat(auto-fill, minmax('+ ($settings.grid_image_size +16) +'px, auto)); grid-auto-rows: minmax(min-content, max-content); }' +
  817. '#content_pane.has_grid #content_grid .image_grid_item' +
  818. '{ grid-column: auto; display:flex; align-items: center; }' +
  819. '#content_pane.has_grid #content_grid .font_grid_item' +
  820. '{ grid-column: 1 / -1; }' +
  821. // FILTER
  822. 'body.theme_dark #grid_btn, body.theme_dark #grid_btn .menu, body.theme_dark #parent_dir_menu, body.theme_dark #shortcuts_menu, body.theme_dark #toggle_sidebar, body.theme_dark #toggle_info' +
  823. '{ filter:invert(100%); }' +
  824. // FLOAT
  825. '#dir_list tr td.name a::before, #checkbox_div label' +
  826. '{ float: left; }' +
  827. '#grid_btn' +
  828. '{ float: right; }' +
  829. // font-family
  830. 'html, body, :root' +
  831. '{ font-family:'+ $settings.UI_font +'; }' +
  832. // font-size
  833. 'html' +
  834. '{ font-size: 100.001%; }' +
  835. 'body, :root, #content_text' +
  836. '{ font-size:'+ $settings.UI_font_size +'; }' +
  837. '#sidebar, #sidebar_header, #dir_list, #tfoot, #content_header, #content_header table, #warnings' +
  838. '{ font-size: 0.875rem; }' +
  839. '#content_grid, .font_grid_item p, #error_message h1, #error_message h2' +
  840. '{ font-size: 1rem; }' +
  841. '#content_grid .font_grid_item h2' +
  842. '{ font-size:'+ $settings.grid_font_size * 4 +'em; }' +
  843. '#content_font' +
  844. '{ font-size:'+ $settings.grid_font_size +'em; }' +
  845. '#content_font .specimen' +
  846. '{ font-size:4em; }' +
  847. '#content_font .hamburger h1' +
  848. '{ font-size: 8em }' +
  849. '#content_font .hamburger h2' +
  850. '{ font-size:6em; text-align:justify; }' +
  851. '#content_font .hamburger h3' +
  852. '{ font-size:2em; }' +
  853. '#content_font .hamburger h4' +
  854. '{ font-size:1.618em; }' +
  855. '.lorem.first:first-line' +
  856. '{ font-size:'+ $settings.grid_font_size * 1.33 +'em; }' +
  857. // font-variant
  858. '.lorem.first:first-line' +
  859. '{ font-variant: small-caps }' +
  860. // font-weight
  861. '#sidebar_title th, h1, h2, h3, h4, h5, h6' +
  862. '{ font-weight: normal }' +
  863. '#dir_list .selected a, body.has_text #text_editor_row, body.edited:not(.has_hidden_text) #text_editor_row' +
  864. '{ font-weight: bold }' +
  865. // HEIGHT
  866. 'html, body, :root, #parent_dir_menu a, #dir_list, #main_content, #content_pane, #content_pdf, #content_text, #content_iframe' +
  867. '{ height: 100%; }' +
  868. '#sidebar_header, #parents_dir_menu, #parents_dir_menu div, #content_pane:not(.has_zoom_image) #content_image, #content_grid div img' +
  869. '{ height: auto; }' +
  870. '#dir_list thead th span::before, #dir_list thead th span::after' +
  871. '{ height: 8px; }' +
  872. '#toggle_sidebar, #toggle_info' +
  873. '{ height: 14px; }' +
  874. '#dir_list td.icon' +
  875. '{ height: 16px; }' +
  876. '#grid_btn, #scale, #prev_next_btns, #close_btn, #reload_btn' +
  877. '{ height: 18px; }' +
  878. '#audio' +
  879. '{ height: 32px; }' +
  880. '.image_grid_item a' +
  881. '{ align-self: center; justify-self: center; }' +
  882. //'#content_grid.has_grid .image_grid_item' +
  883. // '{ height: '+ $grid_image_size +'px; }' +
  884. '#main_content' +
  885. '{ height:'+ window.innerHeight +'px; }' +
  886. // max-height
  887. '#content_pane:not(.has_zoom_image) #content_image' +
  888. '{ max-height: calc(100% - 4em); }' +
  889. '#content_pane.has_zoom_image #content_image' +
  890. '{ max-height: none; }' +
  891. '#content_grid div img' +
  892. '{ max-height:'+ ($settings.grid_image_size) +'px; }' +
  893. // min-height
  894. '#title' +
  895. '{ min-height: 18px; }' +
  896. '#sidebar' +
  897. '{ min-height: 100%; }' +
  898. // hyphens
  899. '#parents_dir_menu div, #content_header td button, #content_font' +
  900. '{ hyphens: none; }' +
  901. 'html, body, :root, .hamburger h3, .lorem' +
  902. '{ hyphens: auto; }' +
  903. // LEFT
  904. '#sidebar ul, #parent_dir_menu, #dir_list thead, #dir_list tbody, #tfoot, #close_audio::after, body.has_hidden_sidebar #toggle_sidebar, #overlay' +
  905. '{ left: 0; }' +
  906. ' #grid_btn ul.menu' +
  907. '{ left: unset; }' +
  908. 'body.has_hidden_sidebar #sidebar_wrapper' +
  909. '{ left: 3px; }' +
  910. 'body.show_numbers #tbody tr td.name a::before' +
  911. '{ left: 6px; }' +
  912. '#warnings, .split_btn::after' +
  913. '{ left: 50%; }' +
  914. '#shortcuts_menu + ul > li > ul' +
  915. '{ left: 100%; }' +
  916. '#dir_list thead th span::before' +
  917. '{ left: -16px; }' +
  918. // letter-spacing
  919. '.lorem.first:first-line, .font_grid_item p' +
  920. '{ letter-spacing: 0.1em }' +
  921. '#sidebar_title th' +
  922. '{ letter-spacing: 0.5em }' +
  923. // line-height
  924. '.image_grid_item a' +
  925. '{ line-height: 0; }' +
  926. '.split_btn span, .font_grid_item p' +
  927. '{ line-height: 1; }' +
  928. '#content_font .specimen' +
  929. '{ line-height: 1.2; }' +
  930. '#dir_list tr a, #title, .lorem' +
  931. '{ line-height: 1.4; }' +
  932. // list-style
  933. '#sidebar ul, #grid_btn .menu li' +
  934. '{ list-style-type: none; }' +
  935. // MARGIN
  936. '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_font .hamburger *, #content_grid p, #content_grid h2, #content_text, #warnings h3' +
  937. '{ margin: 0; }' +
  938. // margin-top
  939. '.image_grid_item + .font_grid_item' +
  940. '{ margin-top:-1px; }' +
  941. '#checkbox_div label input, #show_details, body.is_error #tbody h1' +
  942. '{ margin-top: 0; }' +
  943. 'body.has_audio #dir_list input' +
  944. '{ margin-top: 1px; }' +
  945. // margin-right
  946. '#content_image, #content_video, .image_grid_item a' +
  947. '{ margin-right: auto; }' +
  948. '#increase' +
  949. '{ margin-right: -4px; }' +
  950. 'th input, tr.media input, #scale' +
  951. '{ margin-right: 8px; }' +
  952. '#show_details' +
  953. '{ margin-right: 0.5em; }' +
  954. '#warning_ignore_btn' +
  955. '{ margin-right: 2em; }' +
  956. '#checkbox_div' +
  957. '{ margin-right: calc(-6em - 4px); }' +
  958. // margin-bottom
  959. '#show_details' +
  960. '{ margin-bottom: 0; }' +
  961. 'body.has_audio #dir_list input' +
  962. '{ margin-bottom: 1px; }' +
  963. '#content_grid.has_image_grid' +
  964. '{ margin-bottom: 1rem; }' +
  965. // margin-left
  966. '#content_image, #content_video, .image_grid_item a' +
  967. '{ margin-left: auto; }' +
  968. '#decrease' +
  969. '{ margin-left: -4px; }' +
  970. 'th input' +
  971. '{ margin-left: 2px; }' +
  972. 'tr.media input' +
  973. '{ margin-left: 7px; }' +
  974. '#prev_next_btns' +
  975. '{ margin-left: 8px; }' +
  976. '#show_details, #warnings button' +
  977. '{ margin-left: 0.5em; }' +
  978. '#warnings' +
  979. '{ margin-left: -12em; }' +
  980. '#dir_list tbody tr' +
  981. '{ margin-inline-start:0; }' +
  982. // -webkit-margin
  983. '#sidebar ul' +
  984. '{ -webkit-margin-before:0em !important; -webkit-margin-after:0em !important; -webkit-padding-start:0em; }' +
  985. // Object-fit
  986. '#content_image' +
  987. '{ object-fit:contain; }' +
  988. // OPACITY
  989. '#content_pane.has_ignored::before, #close_audio::after' +
  990. '{ opacity:0.3; }' +
  991. '#close_audio:hover::after' +
  992. '{ opacity:0.6; }' +
  993. '#sidebar_menus td:last-of-type:hover > div, #shortcuts_menu div, #shortcuts_menu div, #parent_dir_menu, #content_pane:hover #prev_btn, #content_pane:hover #next_btn, #toggle_sidebar, #toggle_info, body.use_custom_icons #dir_list tr.file.ignore a.icon::before, .split_btn span' +
  994. '{ opacity: 0.7; }' +
  995. '#content_grid div img' +
  996. '{ opacity:0.8; }' +
  997. '#grid_btn:hover, #parent_dir_menu:hover, #content_pane #prev_btn:hover, #content_pane #next_btn:hover, #toggle_sidebar:hover, #toggle_info:hover, .split_btn span:hover' +
  998. '{ opacity:1; }' +
  999. // outline
  1000. '#grid_btn, #dir_list tbody, #dir_list tbody a, #audio:focus, #content_grid .font_grid_item, #content_font, #warnings button:focus, #content_video' +
  1001. '{ outline: none; }' +
  1002. // OVERFLOW
  1003. 'html, body, :root, #parents_dir_menu div, #shortcuts_menu, #dir_list, #dir_list tbody a, #main_content, #close_audio, .hamburger h1, .hamburger h2, .hamburger h4' +
  1004. '{ overflow: hidden; }' +
  1005. '#dir_list tbody, #next_track, #prev_track, #content_container, #content_font, #content_grid' +
  1006. '{ overflow: auto; }' +
  1007. '#sidebar, #content_font' +
  1008. '{ overflow-wrap: break-word; }' +
  1009. '.lorem' +
  1010. '{ overflow-wrap: normal; }' +
  1011. // PADDING
  1012. '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, #tfoot, #content_pane, #content_pdf, #content_iframe, #content_text, audio::-webkit-media-controls-panel' +
  1013. '{ padding: 0; }' +
  1014. '#sidebar_title th' +
  1015. '{ padding: 4px; }' +
  1016. '.image_grid_item' +
  1017. '{ padding:6px; }' +
  1018. '#error_message, #warnings' +
  1019. '{ padding:1rem; }' +
  1020. // padding-top
  1021. '#dir_list thead th, #dir_list .details, #prev_track, #next_track, #close_audio' +
  1022. '{ padding-top: 0; }' +
  1023. '#show_details, #text_editor_row a, #toggle_info' +
  1024. '{ padding-top: 2px; }' +
  1025. '#parents_dir_menu div, #grid_btn .menu li, #dir_list tbody a, #stats, #title_buttons_left, #title_buttons_right, #title, body #content_audio_title td, #content_audio td' +
  1026. '{ padding-top: 4px; }' +
  1027. '#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, .hamburger h4' +
  1028. '{ padding-top: 6px; }' +
  1029. '#content_header table' +
  1030. '{ padding-top: 7px; }' +
  1031. '#content_grid .font_grid_item, .font_grid_item p, #content_font div.lorem' +
  1032. '{ padding-top: 0.5rem; }' +
  1033. 'body:not(.has_text) #content_pane.has_image:not(.has_grid) #content_container, #content_font div.specimen' +
  1034. '{ padding-top: 2rem; }' +
  1035. // padding-right
  1036. '#sidebar_header tbody tr:last-of-type td, #prev_track, #next_track, #prev_next_btns, #close_audio' +
  1037. '{ padding-right: 0; }' +
  1038. '#scale' +
  1039. '{ padding-right: 4px; }' +
  1040. '#parents_dir_menu div, #show_details, #grid_btn .menu li, #stats, #title_buttons_left, #title_buttons_right, #content_audio td, #close_audio' +
  1041. '{ padding-right: 6px; }' +
  1042. '#parents_dir_menu + ul li a, #shortcuts_menu + ul li a, #shortcuts_menu + ul li > span, #shortcuts_menu + ul li div span, body.show_numbers #tbody tr td.name a::before, #dir_list tbody a' +
  1043. '{ padding-right: 8px; }' +
  1044. '#dir_list td.details, #content_header table, #title' +
  1045. '{ padding-right: 12px; }' +
  1046. '#grid_btn .menu, #content_audio_title td' +
  1047. '{ padding-right: 29px; }' +
  1048. '.font_grid_item p, .font_grid_item a' +
  1049. '{ padding-right: 2rem; }' +
  1050. 'body:not(.has_text) #content_pane.has_image:not(.has_grid) #content_container, #content_font div' +
  1051. '{ padding-right: 2.5rem; }' +
  1052. // padding-bottom
  1053. '#prev_track, #next_track, #close_audio' +
  1054. '{ padding-bottom: 0px }' +
  1055. '#show_details, body.is_dirs_on_top #dir_list tbody tr.sorted:not(:last-of-type)' +
  1056. '{ padding-bottom: 2px; }' +
  1057. '#title, body #content_audio_title td, #text_editor_row a, #title_buttons_left, #title_buttons_right' +
  1058. '{ padding-bottom: 3px;}' +
  1059. '#parents_dir_menu div, #grid_btn .menu li, #dir_list tbody a, #dir_list .details' +
  1060. '{ padding-bottom: 4px; }' +
  1061. '#content_header table, #dir_list tfoot td' +
  1062. '{ padding-bottom: 5px; }' +
  1063. '#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' +
  1064. '{ padding-bottom: 6px; }' +
  1065. '#dir_list thead th.details, .hamburger h4' +
  1066. '{ padding-bottom: 9px; }' +
  1067. '.specimen, .font_grid_item p, .font_grid_item a' +
  1068. '{ padding-bottom: 0.5rem; }' +
  1069. 'body:not(.has_text) #content_pane.has_image:not(.has_grid) #content_container, #content_font div:first-of-type, #content_font .lorem:last-of-type' +
  1070. '{ padding-bottom: 2rem; }' +
  1071. // padding-left
  1072. '#dir_list #tbody td.name a.icon, #dir_list td.details, #grid_btn .menu, #sidebar_header tbody tr:last-of-type td, #prev_next_btns' +
  1073. '{ padding-left: 0px; }' +
  1074. '#dir_list .audio a, #dir_list .video a, #checkbox_div, #scale' +
  1075. '{ padding-left: 4px; }' +
  1076. '#parents_dir_menu div, #dir_list th#name, #dir_list td.icon, #dir_list .icon + td.name a, #dir_list tfoot td:not(#toggle_info), #checkbox_div #parents_dir_menu div, #show_details, #grid_btn .menu li, #title_buttons_left, #title_buttons_right, #content_audio td, #prev_track' +
  1077. '{ padding-left: 6px; }' +
  1078. '#parents_dir_menu + ul li a' +
  1079. '{ padding-left: 8px; }' +
  1080. '#content_header table, #title' +
  1081. '{ padding-left: 12px; }' +
  1082. '#text_editor_row a' +
  1083. '{ padding-left: 18px; }' +
  1084. '#shortcuts li span, #shortcuts li a' +
  1085. '{ padding-left: 20px; }' +
  1086. '#shortcuts ul li a' +
  1087. '{ padding-left: 30px; }' +
  1088. 'body.has_hidden_sidebar #title_buttons_left' +
  1089. '{ padding-left: 22px; }' +
  1090. '#dir_list #tbody tr:not(.media) a span' +
  1091. '{ padding-left: 27px; -webkit-padding-start: 27px; }' +
  1092. '#dir_list thead th#size, #dir_list #tbody td.details.size, #content_audio_title td' +
  1093. '{ padding-left: 29px; }' +
  1094. 'tr.media a.icon' +
  1095. '{ padding-left: 31px; }' +
  1096. '.font_grid_item p, .font_grid_item a' +
  1097. '{ padding-left: 2rem; }' +
  1098. 'body:not(.has_text) #content_pane.has_image:not(.has_grid) #content_container, #content_font div' +
  1099. '{ padding-left: 2.5rem; }' +
  1100. //-webkit-padding
  1101. '#dir_list td.name a' +
  1102. '{ -webkit-padding-start:0; }' +
  1103. '#dir_list .icon + td.name a' +
  1104. '{ -webkit-padding-start:1em; }' +
  1105. // POSITION
  1106. '#sidebar_wrapper, #sidebar_header, #sidebar_buttons, #sidebar_menus td:first-of-type, #sidebar_header tbody tr:last-of-type td, #dir_list, #dir_list thead th span, body.show_numbers #tbody tr td.name a::before, #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, #content_header, #content_grid, #content_text, #content_font, #content_pdf, #content_iframe, #close_audio, #content_grid div img, #content_text, .split_btn' +
  1107. '{ position: relative; }' +
  1108. '#sidebar ul, #handle, #parent_dir_menu, #toggle_sidebar, #toggle_info, body.has_hidden_sidebar #sidebar_wrapper, #dir_list tbody, #close_audio::after, .split_btn::after, #content_container, #content_image, #warnings, #overlay, #content_pane.has_grid #content_grid::after, #content_pane.has_grid .image_grid_item::after' +
  1109. '{ position: absolute; }' +
  1110. '#dir_list thead, #tfoot' +
  1111. '{ position: fixed; }' +
  1112. // RIGHT
  1113. '#sidebar ul, #parent_dir_menu, #grid_btn .menu, #dir_list thead, #dir_list tbody, #tfoot, #close_audio::after, #title_buttons_right, #next_btn, #overlay, #content_pane.has_grid #content_grid::after, #content_pane.has_grid .image_grid_item::after, #toggle_info' +
  1114. '{ right: 0; }' +
  1115. '#dir_list thead th span::after' +
  1116. '{ right: -16px; }' +
  1117. '#toggle_sidebar' +
  1118. '{ right: 4px; }' +
  1119. '#handle' +
  1120. '{ right: -4px; }' +
  1121. // table-layout
  1122. '#dir_list' +
  1123. '{ table-layout: fixed; }' +
  1124. // text-align
  1125. '#sidebar ul, #dir_list tbody, #dir_list th#sort_by_ext, #dir_list tbody .name, #dir_list thead th:not(.details), #dir_list tr.details, #title_buttons_left' +
  1126. '{ text-align: left; }' +
  1127. '#parent_dir_menu a, #parents_dir_menu, th.details, #parents_dir_menu div, #shortcuts_menu div, #content_header, #content_title, #content_audio_title, #content_audio td' +
  1128. '{ text-align: center; }' +
  1129. '#grid_btn .menu li, body.show_numbers #tbody tr td.name a::before, #dir_list th#sort_by_default, #dir_list th#sort_by_kind, #dir_list .size, #dir_list .date, #dir_list .kind, #title_buttons_right, #warnings div, .playlist_time' +
  1130. '{ text-align: right; }' +
  1131. '.hamburger, .lorem' +
  1132. '{ text-align: justify; }' +
  1133. // text-decoration
  1134. 'a, a:hover' +
  1135. '{ text-decoration: none !important; }' +
  1136. // text-transform
  1137. '.font_grid_item p' +
  1138. '{ text-transform: uppercase; }' +
  1139. // TOP
  1140. '#handle, #parent_dir_menu, #dir_list, #toggle_info, #title_buttons_left, #title_buttons_right, #close_audio::after, #prev_btn, #next_btn, #warnings, #overlay, #content_pane.has_grid #content_grid::after, .split_btn::after' +
  1141. '{ top: 0; }' +
  1142. '#shortcuts_menu + ul > li > ul, body.theme_light #grid_btn .menu' +
  1143. '{ top: -1px !important; }' +
  1144. 'body.has_hidden_sidebar #sidebar_wrapper, #dir_list thead th span::before, #dir_list thead th span::after' +
  1145. '{ top: 2px; }' +
  1146. '#toggle_sidebar' +
  1147. '{ top: 4px; }' +
  1148. 'body.theme_dark #grid_btn .menu' +
  1149. '{ top: -7px; }' +
  1150. // TRANSFORM
  1151. '#dir_list' +
  1152. '{ transform: scale(1); }' + // needed to establish #dir_list as containing block for thead and tfoot position fixed.
  1153. 'body.has_hidden_sidebar #toggle_sidebar, #dir_list thead th:not(.up) span::after, #next_track, #next_btn, #toggle_info' +
  1154. '{ transform:rotate(180deg); }' +
  1155. '#close_audio::after' +
  1156. '{ transform:rotate(45deg); }' +
  1157. // user-select
  1158. '#sidebar_header, #content_pane:not(.has_zoom_image) #content_image' +
  1159. '{ user-select:none; -webkit-user-select:none; }' +
  1160. // vertical-align
  1161. '#content_pane' +
  1162. '{ vertical-align:top; }' +
  1163. '#title_buttons_left, #title_buttons_right, #title' +
  1164. '{ vertical-align:top; }' +
  1165. // white-space
  1166. '#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' +
  1167. '{ white-space: normal; }' +
  1168. '#theader span, #tbody td:not(.name), #grid_btn .menu li, .hamburger h1, .hamburger h2, .hamburger h4' +
  1169. '{ white-space: pre; }' +
  1170. // WIDTH
  1171. 'html, body, :root, table, #parent_dir_menu a, #dir_list thead, #tbody, #text_editor_row td, #tbody tr, #shortcuts_menu + ul > li > ul, #grid_btn .menu li, #content_container, #content_header, #content_grid, #content_text, #content_font svg, #content_pdf, #content_iframe' +
  1172. '{ width: 100%; }' +
  1173. '#sidebar_menus td:nth-of-type(2), #dir_list .date, #title, #content_pane:not(.has_zoom_image) #content_image, #content_grid div img' +
  1174. '{ width: auto; }' +
  1175. '.split_btn span' +
  1176. '{ width: 2em; }' +
  1177. '#content_pane.has_grid #content_grid::after, #content_pane.has_grid .image_grid_item::after, .split_btn::after' +
  1178. '{ width: 1px; }' +
  1179. '#handle' +
  1180. '{ width: 8px; }' +
  1181. '#dir_list thead th span::before, #dir_list thead th span::after, #toggle_sidebar, #toggle_info' +
  1182. '{ width: 18px; }' +
  1183. '#sidebar_menus td:nth-of-type(odd)' +
  1184. '{ width: 24px; }' +
  1185. '#grid_btn' +
  1186. '{ width: 28px; }' +
  1187. '#prev_track, #next_track, #close_audio' +
  1188. '{ width: 2rem; }' +
  1189. '#content_pane:not(.has_image) #title_buttons_left, #content_pane:not(.has_image) #title_buttons_right, #content_pane:not(.has_zoom_image) #title_buttons_left, #content_pane:not(.has_zoom_image) #title_buttons_right' +
  1190. '{ width: 4rem; }' +
  1191. '#shortcuts_menu div, #checkbox_div' +
  1192. '{ width: 6em; }' +
  1193. '#content_pane.has_image #title_buttons_left, #content_pane.has_image #title_buttons_right, #content_pane.has_zoom_image #title_buttons_left, #content_pane.has_zoom_image #title_buttons_right, #content_pane.has_grid #title_buttons_left, #content_pane.has_grid #title_buttons_right, #content_pane.has_font #title_buttons_left, #content_pane.has_font #title_buttons_right' +
  1194. '{ width: 9.5em; }' +
  1195. '#warnings' +
  1196. '{ width: 26em; }' +
  1197. 'body.has_hidden_sidebar #sidebar_wrapper' +
  1198. '{ width: 0 !important; }' +
  1199. '#reload_btn, #close_btn' +
  1200. '{ width: 52px; }' +
  1201. 'body.has_hidden_sidebar #content_pane' +
  1202. '{ width: 100% !important; }' +
  1203. 'body:not(.has_hidden_sidebar) #sidebar_wrapper' +
  1204. '{ width:'+ getQuery('width').toString() +'%; }' +
  1205. 'body:not(.has_hidden_sidebar) #content_pane' +
  1206. '{ width:'+ (100 - getQuery('width')).toString() +'%; }' +
  1207. '#content_video' +
  1208. '{ width: calc(100% - 2em); }' +
  1209. // max-width
  1210. 'html, body, :root' +
  1211. '{ max-width: 100%; }' +
  1212. '#content_pane:not(.has_zoom_image) #content_image' +
  1213. '{ max-width: calc(100% - 5em); }' +
  1214. '#content_pane.has_zoom_image #content_image' +
  1215. '{ max-width: none; }' +
  1216. '#sidebar_menus td:nth-of-type(odd)' +
  1217. '{ max-width: 24px; }' +
  1218. '#content_grid div img' +
  1219. '{ max-width:'+ ($settings.grid_image_size).toString() +'px; }' +
  1220. // min-width
  1221. 'body.show_numbers #tbody tr td.name a::before' +
  1222. '{ min-width: 17px; }' +
  1223. 'body:not(.has_hidden_sidebar) #sidebar_menus td:nth-of-type(odd)' +
  1224. '{ min-width: 24px; }' +
  1225. 'body:not(.has_hidden_sidebar) #dir_list' +
  1226. '{ min-width: 100px; }' +
  1227. 'body:not(.has_hidden_sidebar) #sidebar_wrapper' +
  1228. '{ min-width: 220px; }' +
  1229. // will-change
  1230. '#sidebar_wrapper, #content_pane' +
  1231. '{ will-change: width }' +
  1232. // word-break
  1233. '#content_header td button' +
  1234. '{ word-break: none; }' +
  1235. '#title' +
  1236. '{ word-break: break-word; }' +
  1237. '#content_font .specimen' +
  1238. '{ word-break: break-all; }' +
  1239. '.lorem' +
  1240. '{ word-break: normal; }' +
  1241. // Z-INDEX
  1242. '#content_pane' +
  1243. '{ z-index: 0; }' +
  1244. '#handle, #sidebar_wrapper' +
  1245. '{ z-index: 1 !important; }' +
  1246. '#parents_dir_menu + ul, #content_header' +
  1247. '{ z-index: 20; }' +
  1248. '#sidebar_header, #content_pane.has_grid .image_grid_item::after' +
  1249. '{ z-index: 2000; }' +
  1250. '#shortcuts_menu + ul, #toggle_sidebar' +
  1251. '{ z-index: 9997; }' +
  1252. '#overlay' +
  1253. '{ z-index: 9997; }' +
  1254. '#warnings' +
  1255. '{ z-index: 9999; }' +
  1256. 'body.has_hidden_sidebar #sidebar_header' +
  1257. '{ z-index:unset; }'
  1258. ;
  1259. // Gecko Styles:
  1260. const $gecko_style_rules =
  1261. 'html, body.is_gecko { border: solid 1px gray !important; }' +
  1262. 'body.is_gecko button { padding:revert; }' +
  1263. 'body.is_gecko #grid_btn .menu { top:-7px; left:-120px; }' +
  1264. 'body.is_gecko thead {font-size:100%;}' +
  1265. 'body.is_gecko #dir_list .dir::before {position:absolute;}' +
  1266. 'body.is_gecko.use_default_icons:not(.is_converted_list) #dir_list .file .name .icon { padding-left:4px; background:none; }' +
  1267. 'body.is_gecko.use_default_icons #dir_list .file .name .icon img { margin-right:6px; height:14px; }' +
  1268. 'body.is_gecko #tbody > tr > td:not(:first-of-type) { float:left }' +
  1269. 'body.is_gecko #content_audio_title td { padding-top: 6px; }' +
  1270. 'body.is_gecko #content_audio_title td { padding-bottom: 0; }' +
  1271. 'body.is_gecko #prev_track,body.is_gecko #next_track, body.is_gecko #close_audio { background-color: rgba(26,26,26,.8); }' +
  1272. 'body.is_gecko #close_audio::after { filter:invert(100%); opacity:0.5; }' +
  1273. 'body.is_gecko #close_audio:hover::after { opacity:0.75; }' +
  1274. 'body.is_gecko.dark #prev_track, body.is_gecko.dark #next_track { background: #222 '+ $next_track_arrow_gecko +' center no-repeat; }' +
  1275. 'body.is_gecko #prev_track:hover, body.is_gecko #next_track:hover { background: #222 '+ $next_track_arrow_gecko_hover +' center no-repeat; }'
  1276. ;
  1277. var $text_editing_style_rules =
  1278. 'html, body, #iframe_body { margin:0; padding:0; }' +
  1279. // toolbar
  1280. '#toolbar { margin:0; padding:0; height:32px; display:block; position:relative; left:0; right:0; z-index:100; background:#EEE; border:0; border-bottom: solid 1px #999; font-family:'+ $settings.UI_font +'; font-size:'+ parseFloat($settings.UI_font_size) * 0.875 + $settings.UI_font_size.replace(/\d*/,'') +'; -webkit-user-select: none; -moz-user-select: none; user-select:none; }' +
  1281. '#toolbar li { margin:4px; padding:4px; width:3.5em; display:block; opacity:0.5; list-style-type:none; cursor:pointer; }' +
  1282. '#toolbar li:hover, .preview_text:not(.split) #toolbar li#show_preview, body.source_text #show_source, .split_view #toolbar li#toggle_split, .edited #save_btn { opacity:1; }' +
  1283. '#toolbar li#toggle_split { float:left; width:16px; height: 16px; margin: 4px 0 4px 4px; background:url("data:image/svg+xml;utf8,<svg version=\'1.1\' id=\'Layer_1\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' x=\'0px\' y=\'0px\' width=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' enable-background=\'new 0 0 16 16\' xml:space=\'preserve\'><path fill=\'%23333333\' d=\'M0,0v16h16V0H0z M15,15H8.5V1H15V15z M7.5,15H1V1h6.5V15z\'/></svg>") center no-repeat; }' +
  1284. '#toolbar li#sync_scroll { width:8em; float:left; opacity:1; }' +
  1285. '#toolbar li#sync_scroll input { float:left; }' +
  1286. '#toolbar li#sync_scroll label { width:8em; display:block; font-size:inherit; line-height:1.5; }' +
  1287. '#toolbar li#clear_text { height:16px; float:right; text-align:center; }' +
  1288. '#toolbar li#save_btn { float:right; width:20px; height:16px; background: url("data:image/svg+xml;utf8,<svg version=\'1.1\' id=\'Layer_1\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' x=\'0px\' y=\'0px\' width=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' enable-background=\'new 0 0 16 16\' xml:space=\'preserve\'><g> <path fill=\'%23333333\' d=\'M16,0v10.02h-1.33V1.33H1.33v8.69H0V0H16z\'/> <path fill=\'%23333333\' d=\'M8.47,16h-0.7l-3.08-3.08l0.94-0.94l1.83,1.83V4.28h1.33v9.53l1.81-1.82l0.94,0.93L8.47,16z\'/></g></svg>") 8px 4px no-repeat; position:relative; }' +
  1289. '#toolbar li#show_source { float:left; width:16px; height: 16px; margin: 4px 0 4px 12px; background:url("data:image/svg+xml;utf8,<svg version=\'1.1\' id=\'Layer_1\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' x=\'0px\' y=\'0px\' width=\'42px\' height=\'16px\' viewBox=\'0 0 42 16\' enable-background=\'new 0 0 42 16\' xml:space=\'preserve\'><g> <path fill=\'%23333333\' d=\'M0.08,7.02L5.94,3.9l0.43,0.78L1.18,7.42l5.19,2.74l-0.43,0.78L0.08,7.82V7.02z\'/> <path fill=\'%23333333\' d=\'M7.84,16.01H6.82L13.78,0h1.02L7.84,16.01z\'/> <path fill=\'%23444444\' d=\'M21.75,7.87l-5.86,3.12l-0.43-0.78l5.19-2.74l-5.19-2.74l0.43-0.78l5.86,3.12V7.87z\'/> <path fill=\'%23333333\' d=\'M30.98,2.65h-3.63V1.58h8.55v1.07h-3.63v9.65h-1.28V2.65z\'/> <path fill=\'%23333333\' d=\'M36.38,4.71h1.3l0.42-2h0.8v2h2.45v0.99h-2.45v4.63c0,0.74,0.27,1.15,0.96,1.15c0.43,0,1.09-0.21,1.39-0.35 l0.18,0.95c-0.42,0.26-1.18,0.45-1.81,0.45c-1.36,0-1.94-0.7-1.94-2.19V5.71h-1.3V4.71z\'/></g></svg>") left 5px no-repeat; }' +
  1290. '#toolbar li#show_preview { float:left; width:16px; height: 16px; margin: 4px 0 4px 4px; background:url("data:image/svg+xml;utf8,<svg version=\'1.1\' id=\'Layer_1\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' x=\'0px\' y=\'0px\' width=\'42px\' height=\'16px\' viewBox=\'0 0 42 16\' enable-background=\'new 0 0 42 16\' xml:space=\'preserve\'><g> <path fill=\'%23333333\' d=\'M0.08,7.02L5.94,3.9l0.43,0.78L1.18,7.42l5.19,2.74l-0.43,0.78L0.08,7.82V7.02z\'/> <path fill=\'%23333333\' d=\'M7.84,16.01H6.82L13.78,0h1.02L7.84,16.01z\'/> <path fill=\'%23444444\' d=\'M21.75,7.87l-5.86,3.12l-0.43-0.78l5.19-2.74l-5.19-2.74l0.43-0.78l5.86,3.12V7.87z\'/> <path fill=\'%23333333\' d=\'M30.98,2.65h-3.63V1.58h8.55v1.07h-3.63v9.65h-1.28V2.65z\'/> <path fill=\'%23333333\' d=\'M36.38,4.71h1.3l0.42-2h0.8v2h2.45v0.99h-2.45v4.63c0,0.74,0.27,1.15,0.96,1.15c0.43,0,1.09-0.21,1.39-0.35 l0.18,0.95c-0.42,0.26-1.18,0.45-1.81,0.45c-1.36,0-1.94-0.7-1.94-2.19V5.71h-1.3V4.71z\'/></g></svg>") -24px 5px no-repeat; }' +
  1291. '.edited #toolbar li#save_btn { background: url("data:image/svg+xml;utf8,<svg version=\'1.1\' id=\'Layer_1\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' x=\'0px\' y=\'0px\' width=\'16px\' height=\'16px\' viewBox=\'0 0 16 16\' enable-background=\'new 0 0 16 16\' xml:space=\'preserve\'><g> <path fill=\'%23DD2222\' d=\'M16,0v10.02h-1.33V1.33H1.33v8.69H0V0H16z\'/> <path fill=\'%23DD2222\' d=\'M8.47,16h-0.7l-3.08-3.08l0.94-0.94l1.83,1.83V4.28h1.33v9.53l1.81-1.82l0.94,0.93L8.47,16z\'/></g></svg>") 8px 4px no-repeat; position:relative; }' +
  1292. '#toolbar li#save_btn div { display:none; position:relative; top:-9px; left:-24px; color:#999; text-align:right; }' +
  1293. '#toolbar li#save_btn:hover div { display:block; }' +
  1294. '#toolbar li#save_btn span { width:6rem; padding:4px 6px; background: #EEE; float:right; display:block; border:solid 1px #999; position:relative; z-index:1; }' +
  1295. '#toolbar li#save_btn span:first-of-type { border-bottom-width:0px; }' +
  1296. '#toolbar li#save_btn span:hover { color:#444; }' +
  1297. // resize handle
  1298. '.split_view #text_editing_handle { width:9px; position:absolute; top:0; bottom:0; left:calc(50% - 4px); cursor:col-resize; z-index:11; }' +
  1299. '#text_editing_handle { z-index:-1; }' +
  1300. // source
  1301. '#content_source { margin:0; padding: 14px; width:100%; height:calc(100% - 32px); display:block; line-height:1.2; box-sizing:border-box; z-index:1; border:0; overflow-y:scroll; background:#EEE; resize:none; font-family:monospace; font-size:'+ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') +'; }' +
  1302. '#content_source, #content_preview { position:absolute; top:32px; right:0; bottom:0; left:0; }' +
  1303. '#content_source:focus { outline:none; background:#FFF; box-shadow: inset 0 0 4px #888; }' +
  1304. // preview
  1305. '#content_preview { margin:0; padding: 14px; box-sizing:border-box; z-index:1; border:0; overflow-y:scroll; background:#FFF; font-size:'+ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') +'; }' +
  1306. // split views
  1307. '.preview_text:not(.split_view) #content_source, .source_text:not(.split_view) #content_preview, li#save_btn div, .comment { display:none; }' +
  1308. '.split_view #content_preview { border-left:solid 1px #999; }' +
  1309. '.split_view #content_source {width:50%; }' +
  1310. '.split_view #content_preview {left:50%; }' +
  1311. // preview styles
  1312. '#content_preview pre { border:solid 1px #CCC; border-radius:3px; white-space:pre-wrap; word-break:break-word; font-size:'+ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') +'; }' +
  1313. '#content_preview .no_list { list-style:none; }' +
  1314. '#content_preview > .no_list { padding:0; }' +
  1315. '#content_preview .text-align-center { text-align:center; }' +
  1316. '#content_preview th, #content_preview td { vertical-align:top; }' +
  1317. '#content_preview blockquote { margin-top:1em; margin-bottom:1em; color: #555; }' +
  1318. '#content_preview blockquote + blockquote { margin-top:0; }' +
  1319. '.markdown-body input[type="checkbox"] { margin-top:0.375em; margin-right:6px; float:left; }' +
  1320. 'h1 .uplink,h2 .uplink,h3 .uplink,h4 .uplink,h5 .uplink,h6 .uplink { margin:0; padding:0; display:inline-block; font-size:0.875em; cursor:pointer; transition: opacity 0.25s; opacity:0; }' +
  1321. 'h1:hover .uplink,h2:hover .uplink,h3:hover .uplink,h4:hover .uplink,h5:hover .uplink,h6:hover .uplink { transition: opacity 0.25s; opacity:0.5; }' +
  1322. '#content_preview table { font-size:inherit; }' +
  1323. '#content_preview table th { background-color:#EEE; }' +
  1324. '.markdown-body::before, .markdown-body::after { display:none !important; background:transparent; }' +
  1325. // edited warning
  1326. '#iframe_body #warnings p, #iframe_body #warnings button { display:none; }' +
  1327. '#iframe_body #warnings { width:22em; margin-left:-12.5em; padding:1rem; font-size:0.75rem; border-radius:0 0 3px 3px; display:none; position:absolute; top:0; left:50%; z-index:9999; background:#EEE; box-shadow: 0px 2px 12px 0 #111; }' +
  1328. '#iframe_body #warnings h3 { margin:0; font-weight:normal; }' +
  1329. '#iframe_body #warnings div { text-align:right; }' +
  1330. '#iframe_body #warnings button { margin-left:0.5em; }' +
  1331. '#iframe_body #warnings #warning_ignore_btn { margin-right:2em; }' +
  1332. '#iframe_body.has_warning #warnings, #iframe_body.has_warning #warnings.unsaved p#warning_unsaved, #iframe_body.has_warning #warnings.clear p#warning_clear { display:block; }' +
  1333. '#iframe_body.has_warning #warnings.clear #warning_clear_btn, #iframe_body.has_warning #warnings.clear #warning_cancel_btn, #iframe_body.has_warning #warnings.unloading p#warning_unsaved, #iframe_body.has_warning #warnings.unloading #warning_ignore_btn, #iframe_body.has_warning #warnings.unloading #warning_cancel_btn, #iframe_body.has_warning #warnings.unloading #warning_save_btn { display:inline-block; }' +
  1334. // disabled text editing
  1335. '#iframe_body.enable_text_editing #toolbar, #iframe_body.enable_text_editing #preview_text, #iframe_body.enable_text_editing #text_editing_handle { display:none; }' +
  1336. '#iframe_body.enable_text_editing #content_source.disabled { top:0; background:white; }'
  1337. ;
  1338. var $iframe_styles =
  1339. // background
  1340. '#iframe_body a.up, #iframe_body #dir_list { background:transparent; }' +
  1341. '#iframe_body :root, html, body { background-color:#BBB; }' +
  1342. '#iframe_body #thead, #tbody tr { background:rgba(221,221,221,0.5); }' +
  1343. '#iframe_body #thead tr#parent { background:rgba(144,144,144,0.33); }' +
  1344. '#iframe_body #tbody tr:nth-of-type(odd) { background:rgba(221,221,221,1); }' +
  1345. '#iframe_body #tbody tr:hover { background:#BBB; }' +
  1346. // background icons
  1347. '#iframe_body #dir_list tr td.name a::before { content:""; width:14px; height:14px; margin-right:4px; bottom:-1px;}' +
  1348. '#iframe_body #dir_list tr.dir td.name a::before { background: '+ $file_icon_dir +' center no-repeat; background-size:14px 14px; }' +
  1349. '#iframe_body #dir_list tr.file td.name a::before { background: '+ $file_icon_file +' center no-repeat; background-size:14px 14px; }' +
  1350. '#iframe_body #dir_list tr.app td.name a::before { background: '+ $file_icon_app +' center no-repeat; background-size:14px 14px; }' +
  1351. '#iframe_body #dir_list tr.audio td.name a::before { background: '+ $file_icon_audio +' center no-repeat; background-size:14px 14px; }' +
  1352. '#iframe_body #dir_list tr.video td.name a::before { background: '+ $file_icon_video +' center no-repeat; background-size:14px 14px; }' +
  1353. '#iframe_body #dir_list tr.text td.name a::before { background: '+ $file_icon_text +' center no-repeat; background-size:14px 14px; }' +
  1354. '#iframe_body #dir_list tr.image td.name a::before { background: '+ $file_icon_image +' center no-repeat; background-size:14px 14px; }' +
  1355. '#iframe_body #dir_list tr.pdf td.name a::before { background: '+ $file_icon_pdf +' center no-repeat; background-size:14px 14px; }' +
  1356. '#iframe_body #dir_list tr.font td.name a::before { background: '+ $file_icon_font +' center no-repeat; background-size:14px 14px; }' +
  1357. '#iframe_body #dir_list tr.code td.name a::before { background: '+ $file_icon_code +' center no-repeat; background-size:14px 14px; }' +
  1358. '#iframe_body #dir_list tr.htm td.name a::before { background: '+ $file_icon_html +' center no-repeat; background-size:14px 14px; }' +
  1359. '#iframe_body #dir_list tr.invisible td.name a::before { background: '+ $file_icon_invisible +' center no-repeat; background-size:14px 14px; }' +
  1360. '#iframe_body #dir_list tr.ignored td.name a::before { background: '+ $file_icon_ignored +' center no-repeat; background-size:14px 14px; }' +
  1361. '#iframe_body #dir_list.sort_by_default #sort_by_default span::before, #iframe_body #dir_list.sort_by_name #sort_by_name span::before, #iframe_body #dir_list.sort_by_size #sort_by_size span::before, #iframe_body #dir_list.sort_by_date #sort_by_date span::before, #iframe_body #dir_list.sort_by_kind #sort_by_kind span::before, #iframe_body #dir_list.sort_by_ext #sort_by_ext span::before { background-image:'+ $check_mark +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  1362. '#iframe_body #dir_list.sort_by_default #sort_by_default span::after, #iframe_body #dir_list.sort_by_name #sort_by_name span::after, #iframe_body #dir_list.sort_by_size #sort_by_size span::after, #iframe_body #dir_list.sort_by_date #sort_by_date span::after, #iframe_body #dir_list.sort_by_kind #sort_by_kind span::after, #iframe_body #dir_list.sort_by_ext #sort_by_ext span::after { background-image:'+ $up_arrow +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  1363. '#iframe_body .sorting.down span::after { transform:rotate(180deg) }' +
  1364. ':root, html, #iframe_body { border:0 !important; }' + // border
  1365. 'html, #iframe_body { border-radius:0; }' +
  1366. '#iframe_body #dir_list { border-collapse:collapse; }' +
  1367. '#iframe_body #dir_list thead th { border-bottom:solid 1px #999; }' +
  1368. '#iframe_body, iframe_body td.name, iframe_body td.details { box-sizing:border-box; }' + // box-sizing
  1369. '#iframe_body a { color:#111111; }' + // color
  1370. '#iframe_body a:hover { color:#000000; }' +
  1371. '#iframe_body tr.ignore, #iframe_body tr.ignore a { color:grey; }' +
  1372. '.sorting span::before,.sorting span::after { content:""; width:16px; height:8px; display:inline-block; position:relative; }' +// content
  1373. '#iframe_body tbody { counter-reset: row; }' + // counter
  1374. '#iframe_body.show_numbers tr td.name::before { counter-increment: row; content:counter(row); margin-right:6px; min-width:1.25em; text-align:right;}' +
  1375. '#iframe_body #dir_list th.sorting { cursor: pointer; }' + // cursor
  1376. '#iframe_body #dir_list tr::before, #iframe_body #dir_list td.name input, #iframe_body td.ext { display:none; }' + // display
  1377. '#iframe_body a { display:block; }' +
  1378. '#parent + tr { display: grid; grid-gap:0; grid-template-columns: 50% 50%; }' +
  1379. '#iframe_body #dir_list #tbody tr, #sorting_row { display: grid; grid-gap:0; grid-template-columns: minmax(200px,100%) minmax(auto,6em) minmax(auto,14em) minmax(auto,7em); }' +
  1380. '#iframe_body td.name, #iframe_body #sort_by_name, #iframe_body #sort_by_ext { grid-column: 1; }' +
  1381. '#iframe_body td.size, #iframe_body #sort_by_default, #iframe_body #sort_by_size { grid-column: 2; }' +
  1382. '#iframe_body td.date, #iframe_body #sort_by_date { grid-column: 3; }' +
  1383. '#iframe_body td.kind, #iframe_body #sort_by_kind { grid-column: 4; }' +
  1384. '#iframe_body #dir_list td.name::before, #iframe_body #dir_list a::before { float:left; }' + // float
  1385. '#iframe_body { font-family:'+ $settings.UI_font +'; }' + // fonts
  1386. '#iframe_body { font-size:'+ parseFloat($settings.UI_font_size) * 0.875 + $settings.UI_font_size.replace(/\d*/,'') +'; }' +
  1387. '#iframe_body #dir_list, #iframe_body #thead { font-size: 1em; }' +
  1388. '#iframe_body, #iframe_body #dir_list { height:100%; }' + // height
  1389. '#iframe_body #content_source { height:inherit; }' +
  1390. '#iframe_body #thead { height:2em; max-height:2em; }' +
  1391. '#iframe_body #tbody tr { line-height:1.5; }' + // line-height
  1392. 'html, body, #iframe_body { margin:0; }' + // margin
  1393. '#iframe_body .dir::before, #iframe_body .file > img { margin-inline-end: 6px; }' +
  1394. '#iframe_body #dir_list tr, #iframe_body #tbody tr:hover { outline:0; }' + // outline
  1395. '#iframe_body, #iframe_body #dir_list, .details.date { overflow:hidden; text-overflow: ellipsis; }' + // overflow
  1396. '#iframe_body #dir_list #tbody { overflow:auto; }' +
  1397. 'html, :root, #iframe_body, body { padding:0; }' + // padding
  1398. '#iframe_body #tbody tr td, #iframe_body #thead tr th { padding-top:3px; }' +
  1399. '#iframe_body #tbody tr td { padding-right: 6px; }' +
  1400. '#iframe_body #tbody tr td, #iframe_body #thead tr th { padding-bottom:3px; }' +
  1401. '#parent th, #iframe_body #tbody tr td.details.kind { padding-right: 1em; }' +
  1402. '#iframe_body td.details { padding-left: 6px; }' +
  1403. '#parent th, #tbody tr td.name { padding-left:1em; }' +
  1404. '#iframe_body #dir_list, #iframe_body #dir_list tr td.name a::before { position:relative; }' + // position
  1405. '#iframe_body, #iframe_body #dir_list #tbody { position:absolute; right:0; bottom:0; left:0; }' +
  1406. '#iframe_body th { text-align:center; }' + // text-align
  1407. '#iframe_body th:last-of-type, #iframe_body td.details { text-align:right; }' +
  1408. '#iframe_body th:first-of-type { text-align:left; }' +
  1409. '#iframe_body a, #iframe_body a:hover, #iframe_body td:hover { text-decoration:none !important; }' + // text-decoration
  1410. '#iframe_body a.icon { text-indent:2px; }' + // text-indent
  1411. '#iframe_body #thead { user-select:none; -webkit-user-select:none; }' +
  1412. // vertical-align
  1413. '#iframe_body td.details { vertical-align:top; }' +
  1414. // width
  1415. 'html, #iframe_body, #iframe_body #dir_list, #iframe_body #dir_list tr { width:100%; max-width:100%; min-width:100%; }' +
  1416. '#iframe_body #dir_list td:not(:first-child) { width:unset; }' +
  1417. // white-space
  1418. '#iframe_body td:not(.name) { white-space:pre; }'
  1419. ;
  1420.  
  1421. // ADD STYLES
  1422. const $font_styles = document.createElement('style');
  1423. const $font_grid_styles = document.createElement('style');
  1424.  
  1425. function addStyles(el) {
  1426. el.find('style, link[rel="stylesheet"], link[href$="css"]').remove(); // remove any existing stylesheets
  1427. el.append('<style>'+ $main_style_rules +'</style>');
  1428. el.append('<style>'+ $gecko_style_rules +'</style>');
  1429. el.append($font_styles); // empty until font is previewed
  1430. el.append($font_grid_styles); // empty until font grid is made
  1431. el.append('<style>'+ $text_editing_style_rules +'</style>');
  1432. el.append('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>');
  1433. }
  1434. // ***** END STYLES ***** //
  1435.  
  1436. // ***** BUILD MENUS ***** //
  1437. // Parent and Parents Menus
  1438. function updateQueryStr(str) {
  1439. str = str.replace(/([^\?]*)selected=[^&]*(.*)$/m,'$1$2') // delete current selected query, if any
  1440. .replace(/([^\?]*)history=(\d+)\+*([^&]*)(&*)(.*)/m,'$1$4$5&selected=$2&history=$3').replace(/&{2,}/g,'&').replace(/\?&/m,'\?'); // format query with selected and history at end
  1441. if ( str.endsWith('&history=') ) { str = str.replace(/(.+)&history=$/m,'$1'); } // if no history, delete query
  1442. return str;
  1443. }
  1444. function createParentLinks() {
  1445. let $links = [];
  1446. let str = decodeURIComponentSafe(window.location.search);
  1447. str = str.replace('/','').replace('%2F','');
  1448. let $linkPieces = $location.split('/');
  1449. $linkPieces = $linkPieces.slice(2,-2); // remove beginning and ending empty elements and current directory
  1450. while ( $linkPieces.length > 0 ) { // while there are link pieces...
  1451. str = updateQueryStr(str); // update selected and history
  1452. let link = $protocol +'//'+ $linkPieces.join('/') +'/'+ str; // assemble link
  1453. $links.push(link); // add to link array
  1454. $linkPieces.pop(); // remove last link piece and repeat...
  1455. }
  1456. return $links;
  1457. }
  1458. function createParentLinkItems() {
  1459. let $parent_link_menu_items = [];
  1460. let $links = createParentLinks();
  1461. $parent_dir_menu.find('a').attr( 'href', $links[0] ); // set parent link
  1462. for ( let i = 0; i < $links.length; i++ ) {
  1463. let display_name = $links[i].slice(0,$links[i].lastIndexOf('?') - 1);
  1464. display_name = display_name.replace(/\//g,'\/<wbr>');
  1465. let menu_item = '<li><a href="'+ $links[i] +'">' + display_name + '/</a></li>';
  1466. $parent_link_menu_items.push(menu_item);
  1467. }
  1468. return $parent_link_menu_items; // return parents link items
  1469. }
  1470. function updateParentLinks() { $parents_dir_menu.siblings('ul').empty().append( createParentLinkItems() ); }
  1471.  
  1472. // MENUS: User Shortcuts
  1473. function shortcutMenuItems() {
  1474. const $shortcuts = $settings.shortcuts;
  1475. let menu_items = [];
  1476. let $links_arr = [];
  1477. let $links_arr_str = '';
  1478. let $links;
  1479. if ( $shortcuts.length > 0 ) {
  1480. for ( let i = 0; i < $shortcuts.length; i+=1 ) {
  1481. $links = $shortcuts[i].links;
  1482. // make array of links
  1483. for ( let j = 0; j < $links.length; j+=1 ) {
  1484. if ( !$links[j].link.endsWith('/') ) {
  1485. $links[j].link = $links[j].link.slice(0,$links[j].link.lastIndexOf('/') + 1) + '?file=' + $links[j].link.slice($links[j].link.lastIndexOf('/') + 1) ;
  1486. }
  1487. $links_arr[j] = '<li><a href="'+ $links[j].link +'">' + $links[j].link_name + '</a></li>';
  1488. }
  1489. $links_arr_str = $links_arr.join('');
  1490. menu_items[i] = '<li class="bookmarks has_submenu"><a>'+ $shortcuts[i].menu_title +'</a><ul>'+ $links_arr_str +'</ul></li>';
  1491. }
  1492. menu_items = menu_items.join('');
  1493. }
  1494. return menu_items;
  1495. }
  1496.  
  1497. // MENUS: Other menu items
  1498. const $sort_by = $('<li class="has_submenu rule" id="sort_by"><span>Sort by&hellip;</span><ul id="sort_menu"><li id="name"><span>Name</span></li><li id="size"><span>Size</span></li><li id="date"><span>Date</span></li><li id="kind"><span>Kind</span></li><li id="ext"><span>Extension</span></li><li id="default"><span>Default</span></li></ul>');
  1499. const $autoload_media = $('<li class="toggle_UI_pref rule" id="autoload_media"><span id="autoload_media_menu">Autoload Media</span></li>');
  1500. const $theme = $('<li id="theme"><div><span class="toggle_UI_pref" id="theme_dark">Dark Theme</span><span class="toggle_UI_pref" id="theme_light">Light Theme</span></div></li>');
  1501. const $alternate_background = $('<li class="toggle_UI_pref" id="alternate_background"><span>Alternate Backgrounds</span></li>');
  1502. const $show_numbers = $('<li class="toggle_UI_pref rule" id="show_numbers"><span>Show Numbers</span></li>');
  1503. const $text_editing = $('<li class="has_submenu" id="text_editing"><span>Text Editing</span><ul id="text_editing_menu"><li id="text_editor_menu_item" class="rule"><span id="text_editor">Toggle Text Editor</span></li><li id="split_view" class="toggle_UI_pref rule"><span id="toggle_split_view">Split View</span></li><li id="preview_text_menu_item"><span class="toggle_UI_pref" id="source_text">Source Text</span><span class="toggle_UI_pref" id="preview_text">Preview Text</span></li></ul>');
  1504. const $disable_text_editing = $('<li class="rule"><span class="toggle_UI_pref" id="enable_text_editing">Disable Text Editing</span></li>');
  1505. const $default_settings = $('<li><a href="#" id="default_settings" title="Delete URL query string and reload page.">Default User Settings</a></li>');
  1506. const $export_settings = $('<li class="rule"><a href="#" id="export_settings" title="Export user settings to text file.">Export User Settings</a></li>');
  1507. const $contact_link = $('<li><a id="contact" href="mailto:mshroud@vivaldi.net">Contact</a></li>');
  1508. const $donate_link = $('<li><a id="donate" href="https://paypal.me/mschrauzer" target="_blank">Donate</a></li>');
  1509.  
  1510. function assembleMenus() { // called by buildUI();
  1511. $parents_dir_menu.siblings('ul').empty().append( createParentLinkItems() );
  1512. $shortcuts_menu.siblings('ul').append( shortcutMenuItems(), $sort_by, $theme, $alternate_background, $show_numbers, $autoload_media, $text_editing, $disable_text_editing, $default_settings, $export_settings, $contact_link, $donate_link );
  1513. }
  1514. // ***** END BUILD MENUS ***** //
  1515.  
  1516. // ***** INDEX PREP ***** //
  1517. //
  1518. function getIndexType() {
  1519. // Try to determine index type from parent directory link container,
  1520. // with fallbacks for indexes that don't have parent directories,
  1521. // or for parent directory links that aren't siblings or ancestors of the index itself.
  1522. let parentLinkParent; // Possible elements: pre, li, td, th, div -- used to determine index type
  1523. // Try to find parent directory link:
  1524. let parentLink = $('a:contains("Parent Directory"), a:contains("parent directory"), a[href="../"], a[href="/"], a[href^="?"], img[alt*="PARENTDIR"]');
  1525. if ( parentLink.length === 0 ) {
  1526. parentLinkParent = $('body').find('> ul li, > pre, > table:last-of-type tr td');
  1527. } else {
  1528. parentLinkParent = parentLink.parent(); // use original found parentLink
  1529. }
  1530. // If no parentLinkParent found, type = error; else return parent node name
  1531. let nodeName = ( parentLinkParent[0] !== undefined ? parentLinkParent[0].nodeName.toLowerCase() : '');
  1532. if ( parentLinkParent.length === 0 ) {
  1533. nodeName = 'error';
  1534. } else if ( $protocol.startsWith('file') ) {
  1535. if ( navigator.userAgent.indexOf('Firefox') > 0 ) {
  1536. nodeName = 'gecko';
  1537. }
  1538. }
  1539. let types = {'gecko':'gecko','li':'list','pre':'pre','th':'table','td':'table','div':'default','error':'error'};
  1540. let type = types[nodeName];
  1541.  
  1542. if ( type === 'table' ) {
  1543. parentLinkParent.closest('table').addClass('PARENTTABLE');
  1544. }
  1545. if ( type === 'list' ) {
  1546. parentLink.closest('ul').addClass('PARENTLIST');
  1547. }
  1548. return type;
  1549. }
  1550.  
  1551. // Return Index items, Index type, remove parent directory link, and add body class.
  1552. function getIndexItems(el) {
  1553. const type = getIndexType();
  1554. let items;
  1555.  
  1556. switch(type) {
  1557. case 'gecko':
  1558. $(el).addClass('is_converted_gecko');
  1559. items = $('body').find('> table > tbody');
  1560. break;
  1561. case 'list':
  1562. $(el).addClass('is_converted_list');
  1563. items = $('body').find('> ul');
  1564. break;
  1565. case 'pre':
  1566. $(el).addClass('is_converted_pre');
  1567. items = $('body').find('> pre').html();
  1568. break;
  1569. case 'table':
  1570. case 'td':
  1571. $(el).addClass('is_converted_table');
  1572. if ( $('table.PARENTTABLE > tbody').length === 1 ) {
  1573. items = $('table.PARENTTABLE > tbody');
  1574. } else {
  1575. items = $('table.PARENTTABLE'); // tables without tbody
  1576. }
  1577. break;
  1578. case 'default': // local chrome default
  1579. $(el).addClass('is_default');
  1580. items = $('body').find('> table').find('> tbody');
  1581. break;
  1582. case 'error': // error
  1583. $(el).addClass('is_error');
  1584. items = $('body').html();
  1585. break;
  1586. }
  1587. return [items,type];
  1588. }
  1589.  
  1590. // Index Prep: convert rows and return array of rows, with link, size, date-modified
  1591. function convertIndexItems(type,items) {
  1592. let converted = [];
  1593. switch(type) {
  1594. case 'gecko':
  1595. converted = convertGeckoType(items);
  1596. break;
  1597. case 'list':
  1598. converted = convertListType(items);
  1599. break;
  1600. case 'pre':
  1601. converted = convertPreType(items);
  1602. break;
  1603. case 'table':
  1604. case 'default': // local chrome indexes
  1605. converted = convertTableType(type,items);
  1606. break;
  1607. case 'error':
  1608. converted = convertErrorType(items);
  1609. break;
  1610. }
  1611. return converted;
  1612. }
  1613. // Index Prep: convert list type function
  1614. function convertGeckoType(items) {
  1615. let preppedIndex = [];
  1616. const rows = Array.from(items.find('> tr'));
  1617. for ( let row of rows ) {
  1618. let preppedRow = [];
  1619. let cellContents = '';
  1620. let cells = Array.from( $(row).find('> td') );
  1621. let link = ($(cells).find('a').attr('href'));
  1622. for ( let cell of cells ) {
  1623. cellContents = cell.innerText;
  1624. cellContents = ( cellContents !== undefined ? cellContents.trim() : '');
  1625. preppedRow.push(cellContents);
  1626. }
  1627. preppedRow[1] = preppedRow[1].replace(' KB','000'); // convert reported size in KB to total bytes
  1628. preppedRow[2] = preppedRow[2] + ' '+ preppedRow[3];
  1629. preppedRow = preppedRow.slice(1,-1);
  1630. if ( link.length > 0 && link !== '/' && link !== '../' ) {
  1631. preppedRow.unshift(link);
  1632. }
  1633. if ( preppedRow.length > 0 ) { preppedIndex.push(preppedRow); }
  1634. }
  1635. return preppedIndex;
  1636. }
  1637. // Index Prep: convert list type function
  1638. function convertListType(items) {
  1639. let preppedIndex = [];
  1640. const rows = Array.from(items.find('li'));
  1641. for ( let row of rows ) {
  1642. if ( row.innerHTML.indexOf('Parent Directory') === -1 ) {
  1643. let preppedRow = [];
  1644. let link = $(row).find('a').attr('href');
  1645. row = row.innerHTML.replace(/<a .+?<\/a>\s*/,'');
  1646. let cells = row.split(' ');
  1647. for ( let cell of cells ) {
  1648. if ( cell.trim().length > 0 ) {
  1649. preppedRow.push(cell);
  1650. }
  1651. }
  1652. if ( link.length > 0 && link !== '/' && link !== '../' ) {
  1653. preppedRow.unshift(link);
  1654. }
  1655. if ( preppedRow.length > 0 ) {
  1656. preppedIndex.push(preppedRow);
  1657. }
  1658. }
  1659. }
  1660. return preppedIndex;
  1661. }
  1662. // Index Prep: convert pre type function
  1663. function convertPreType(items) {
  1664. let preppedIndex = [];
  1665. items = items.replace(/<a /g,' <a ').replace(/<\/a>/g,'</a> ') // ensure two spaces after file links
  1666. .replace(/\[\s*?\]/g,'[]'); // remove empty 'alt=[ ]'
  1667. const rows = items.split('\n');
  1668. const spaces = /\s{2,}/; // assumes pre type only uses spaces between "columns"
  1669.  
  1670. for ( let row of rows ) {
  1671. let preppedRow = [];
  1672. row = row.replace(/(<a[^>]+?>).+?<\/a>/g,'$1'); // remove link display name because some have 2+ spaces, leading to incorrect split for cells array
  1673. let cells = row.split(spaces);
  1674. let link;
  1675. for ( let cell of cells ) {
  1676. if ( cell.trim().length > 0 ) {
  1677. if ( cell.indexOf('href="') > 0 && cell.search(/href="[?|\/"]|Parent Directory/) === -1 ) {
  1678. link = $(cell).attr('href');
  1679. } else {
  1680. if ( cell.search(/href="|<img |<hr>/) === -1 ) {
  1681. preppedRow.push(cell);
  1682. }
  1683. }
  1684. }
  1685. }
  1686. if ( link !== undefined ) { preppedRow.unshift(link); } // add link to front of preppedRow
  1687. if ( preppedRow.length > 1 ) { preppedIndex.push(preppedRow); }
  1688. }
  1689. return preppedIndex;
  1690. }
  1691. // Index Prep: convert table type function
  1692. function convertTableType(type,items) { // for local chrome indexes and server-generated table-type indexes
  1693. let preppedIndex = [];
  1694. const rows = Array.from(items.find('> tr:not(.PARENT)'));
  1695. for ( let row of rows ) {
  1696. if (row.innerText.search(/Parent Directory/) === -1 ) {
  1697. let preppedRow = [];
  1698. let cells = Array.from( $(row).find('td') );
  1699. let link = $(cells).find('a').attr('href');
  1700. for ( let cell of cells ) {
  1701. cell = cell.innerHTML.trim();
  1702. if ( cell.length > 0 && cell.search(/^&nbsp;$|href="|<img /m) === -1 ) {
  1703. preppedRow.push( cell );
  1704. }
  1705. }
  1706. if ( link !== undefined ) { preppedRow.unshift(link); }
  1707. if ( preppedRow.length > 1 ) { preppedIndex.push(preppedRow); } // preppedRow.length > 2 in order to omit parent directory row
  1708. }
  1709. }
  1710. return preppedIndex;
  1711. }
  1712. // Index Prep: convert error pages (page not found, etc.)
  1713. function convertErrorType(items) {
  1714. items = items.replace(/ style="[^"]*?"/g,'').replace(/<hr>/g,'');
  1715. items = '<div id="error_message"><div id="error_message_icon"></div>'+ items +'</div>';
  1716. return items;
  1717. }
  1718.  
  1719. // Index Prep: Build new Index from prepped rows
  1720. function buildNewIndex(preppedIndex,sort) {
  1721. let newIndexItems = [];
  1722. let i = 0;
  1723. for ( let row of preppedIndex ) {
  1724. // Get row attrs and text
  1725. var rowLink = row[0] !== undefined ? row[0].replace(/&amp;/g,'&') : '';//.replace(//,''); // decode some reserved characters
  1726. const rowName = getItemName(rowLink).replace(/^\s/m,'\&nbsp;').replace(/^\//m,'').replace(/([-_——])/g,'$1<wbr>'); // display name, with word breaks added after unbreakable chars
  1727. const rowSortName = getItemName(rowLink).replace(/^\//m,'').replace('/','').toLocaleLowerCase();
  1728. const sizesAndDates = getItemSizeAndDate(row);
  1729. const rowSize = sizesAndDates[0];
  1730. const rowSortSize = sizesAndDates[1];
  1731. const rowDate = sizesAndDates[2];
  1732. const rowSortDate = sizesAndDates[3];
  1733. const rowExt = getItemExt(rowLink);
  1734. const rowSortKind = getItemKind(rowExt);
  1735. const rowKind = rowSortKind.slice(0,1).toUpperCase() + rowSortKind.slice(1);
  1736. const rowClasses = getItemClasses(rowName,rowSortKind,rowExt);
  1737. // Assemble row elements
  1738. let newRow = $('<tr></tr>').attr('id','rowid-'+ i).addClass(rowClasses).attr('data-kind',rowSortKind).attr('data-ext',rowExt);
  1739. const checkbox = ( rowClasses.indexOf('media') > 0 ? $('<input type="checkbox" tabindex="-1" checked="true" />') : '' );
  1740. const nameSpan = $('<span></span>').append(checkbox, rowName);
  1741. const cellLink = $('<a></a>').addClass('icon').attr('href',rowLink).append(nameSpan);
  1742. const cellName = $('<td></td>').addClass('name').attr('data-name',rowSortName).append(cellLink);
  1743. const cellSize = $('<td></td>').addClass('size details').attr('data-size',rowSortSize).append(rowSize);
  1744. const cellDate = $('<td></td>').addClass('date details').attr('data-date',rowSortDate).append(rowDate);
  1745. const cellKind = $('<td></td>').addClass('kind details').attr('data-kind',rowSortKind).append(rowKind);
  1746. const cellExt = $('<td></td>').addClass('ext details').attr('data-ext',rowExt);
  1747. // Assemble row
  1748. newRow.append(cellName, cellSize, cellDate, cellKind, cellExt);
  1749. newIndexItems.push(newRow[0]);
  1750. i++;
  1751. }
  1752. if ( sort === undefined ) { sort = getQuery('sort_by'); }
  1753. let sortedIndexItems = sortDirList($(newIndexItems), 'sort_by_'+ sort, 1); // initial sort
  1754. return sortedIndexItems;
  1755. }
  1756. // Index Prep: get row name
  1757. function getItemName(link) {
  1758. let name;
  1759. try { name = decodeURIComponentSafe(link); }
  1760. catch (error) { name = link.replace(/%20/g,' ').replace(/\s{2,}/,' '); }
  1761.  
  1762. if ( name.split('/').length > 2 ) { // get name only if x = full path
  1763. let arr = name.split('/');
  1764. if ( name.startsWith('/') && name.endsWith('/') ) { // dirs
  1765. name = arr[arr.length - 2] + '/';
  1766. } else {
  1767. name = arr[arr.length - 1]; // files
  1768. }
  1769. }
  1770. return name;
  1771. }
  1772. // Index Prep: get row classes
  1773. function getItemClasses(name,kind,ext) {
  1774. let itemClasses = [];
  1775. itemClasses.push(kind);
  1776. if ( ext.search(/dir|app/) === -1 ) {
  1777. itemClasses.push('file');
  1778. }
  1779. if ( name.indexOf('.') === 0 ) {
  1780. itemClasses.push('invisible');
  1781. }
  1782. if ( !JSON.stringify($row_types).match( escapeStr(ext) ) ) { // else classify as "other" if extension is not in $row_types.
  1783. itemClasses.push('other'); //itemType = 'Other'; itemKind = 'other';
  1784. } else if ( kind === '' ) {
  1785. // itemClasses.push('code'); //itemType = 'Code'; itemKind = 'code';
  1786. } else {
  1787. itemClasses.push(ext);
  1788. }
  1789. if ( $row_settings.ignore.includes( ext ) ) {
  1790. itemClasses.push('ignore');
  1791. }
  1792. if ( $row_settings.exclude.includes( ext ) ) {
  1793. itemClasses.push('exclude');
  1794. }
  1795. if ( kind === 'audio' || kind === 'video' ) {
  1796. itemClasses.push('media');
  1797. }
  1798. itemClasses = Array.from(new Set(itemClasses)); // remove dupe classes
  1799. return itemClasses.join(' ');
  1800. }
  1801. // Index Prep: get formatted row size and date
  1802. function getItemSizeAndDate(cells) {
  1803. let sizesAndDates = [];
  1804. let rowDisplaySize, rowSortSize, rowDisplayDate, rowSortDate;
  1805. if ( cells.length > 1 ) {
  1806. if ( cells[1].search(/[-:\/]/) !== -1 ) { // test for typical date/time separators.
  1807. rowDisplayDate = cells[1];
  1808. rowDisplaySize = cells[2];
  1809. } else {
  1810. rowDisplayDate = cells[2];
  1811. rowDisplaySize = cells[1];
  1812. }
  1813. }
  1814. // size
  1815. let sizeUnits = /[BYTES|B|K|KB|MB|GB|TB|PB|EB|ZB|YB]/;
  1816. if ( rowDisplaySize === undefined || rowDisplaySize === '' || rowDisplaySize === '-' ) {
  1817. rowDisplaySize = '&mdash;'; rowSortSize = '0'; // if no size supplied, use these defaults
  1818. } else {
  1819. rowSortSize = getItemSortSize(rowDisplaySize);
  1820. if ( !rowDisplaySize.toUpperCase().match(sizeUnits) ) { // if provided size is only numeric
  1821. rowDisplaySize = formatBytes(rowDisplaySize,1);
  1822. } else {
  1823. rowDisplaySize = rowDisplaySize.replace(/(\d+)\s*([A-z])/,'$1 $2');
  1824. }
  1825. }
  1826. // date
  1827. if ( rowDisplayDate === undefined || rowDisplayDate === '' || rowDisplayDate === '-' ) {
  1828. rowDisplayDate = '&mdash;'; rowSortDate = '0';
  1829. } else {
  1830. rowSortDate = getItemDate(rowDisplayDate);
  1831. }
  1832. sizesAndDates.push(rowDisplaySize,rowSortSize,rowDisplayDate,rowSortDate);
  1833. return sizesAndDates;
  1834. }
  1835. // Index Prep: get row size for sorting
  1836. function getItemSortSize(val) {
  1837. let sortSize;
  1838. let values = val.replace(/(\d+)\s*([A-z])/,'$1 $2').split(' ');
  1839. let size = values[0];
  1840. let unit = values[1];
  1841. if ( unit !== undefined ) { unit = unit.toUpperCase(); }
  1842. const factor = { '':1, B:1, K:1000, KB:1000, M:1000000, MB:1000000, G:1000000000, GB:1000000000, T:1000000000000, TB:1000000000000, P:1000000000000000, PB:1000000000000000, E:1000000000000000000, EB:1000000000000000000, Z:1000000000000000000000, ZB:1000000000000000000000 }; // unit to file size
  1843. sortSize = size * factor[unit]; // convert byte size to multiplication factor
  1844. return sortSize;
  1845. }
  1846. // convert numeric sizes to display format
  1847. function formatBytes(val, decimals) {
  1848. if (val === 0) return '0 Bytes';
  1849. const k = 1024;
  1850. const dm = decimals < 0 ? 0 : decimals;
  1851. const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  1852. const i = Math.floor(Math.log(val) / Math.log(k));
  1853. return parseFloat((val / Math.pow(k, i)).toFixed(dm)) +' '+ sizes[i];
  1854. }
  1855. // process date
  1856. function processDate(match,p1,p2,p3) { //date formats: 2017-10-09 13:12 || 2015-07-25T02:02:57.000Z || 12-Mon-2017 21:11
  1857. const mo = 'JanFebMarAprMayJunJulAugSepOctNovDec'.indexOf(p2)/3 + 1; // e.g., convert month into number, or use number
  1858. return p3 +'-'+ mo +'-'+ p1;
  1859. }
  1860. // Index Prep: get row date 2015-07-25T02:22:00.000Z
  1861. function getItemDate(val) {
  1862. let sortDate = val.replace(/^(\d{2})-(\w{3})-(\d{4})/m, processDate) // convert Month to number
  1863. .replace(/\b(\d{1})[-:\/]/g,'0$1/') // add leading 0 for single digit numbers
  1864. .replace(/(\d{2})\/(\d{2})\/(\d{2}),/,'$3$1$2') // reorder MM/DD/YY dates
  1865. .replace(/-|:|\s+|\//g,''); // remove spacing characters
  1866. return sortDate;
  1867. }
  1868. // Index Prep: get row kind
  1869. function getItemKind(ext) {
  1870. let kind = '';
  1871. if ( ext === 'dir' || ext === 'app' ) {
  1872. kind = ext;
  1873. } else {
  1874. for ( let types in $row_types ) {
  1875. if ( $row_types[types].includes( ext ) ) {
  1876. kind = types;
  1877. return kind;
  1878. }
  1879. }
  1880. }
  1881. if ( kind === '' ) { kind = 'other'; }
  1882. return kind;
  1883. }
  1884. // Index Prep: get row extension
  1885. function getItemExt(link) {
  1886. let ext = '';
  1887. if ( ext.endsWith('app/') || ext.endsWith('exe') ) {
  1888. ext = 'app';
  1889. } else if ( link.endsWith('/') ) {
  1890. ext = 'dir';
  1891. } else if ( link.indexOf('.') < 0 ) { // if no '.' in link, ...
  1892. ext = link.slice(link.lastIndexOf('/') + 1).toLowerCase();
  1893. } else { // find the last . and get the remaining characters
  1894. ext = link.slice(link.lastIndexOf('.') + 1).toLowerCase();
  1895. }
  1896. return ext;
  1897. }
  1898. // END INDEX PREP
  1899. // ***** MAKE NEW INDEX ***** //
  1900. function makeNewIndex(el,sort) {
  1901. const indexItems = getIndexItems(el);
  1902. const items = indexItems[0];
  1903. const type = indexItems[1];
  1904. const convertedIndex = convertIndexItems( type, items ); // = array of rows: ["link","date","size"]
  1905. if ( type === 'error' ) {
  1906. return [convertedIndex];
  1907. } else {
  1908. let newIndex = buildNewIndex( convertedIndex, sort );
  1909. return [newIndex];
  1910. }
  1911. }
  1912. function appendNewIndex(body_top) { // setUpUI();
  1913. const newIndex = makeNewIndex(body_top);
  1914. $dir_list_body.append( newIndex);
  1915. $tfoot.find('#stats').html(getIndexStats($dir_list.find('#tbody tr')));
  1916. }
  1917. // get and set index stats
  1918. function getIndexStats(el) {
  1919. const total = el.length;
  1920. const dirs = el.filter('.dir').length;
  1921. const files = el.filter('.file').length;
  1922. const stats = total +' items: '+ dirs +' directories, '+ files +' files';
  1923. return stats;
  1924. }
  1925. // ***** END DIR_LIST SETUP ***** //
  1926.  
  1927. // ***** UI SETUP ***** //
  1928. // Build UI: Append all assembled elements to $body
  1929. function setUpBody() {
  1930. if ( window.self === window.top ) { // if it's an iframe...
  1931. $('head').prepend('<meta charset="utf-8">');
  1932. $('body').attr('id','top').attr('lang','en').find('script').remove();
  1933. } else {
  1934. $('body').attr('id','iframe_body');
  1935. }
  1936. }
  1937. function buildUI() {
  1938. setUpBody();
  1939. addStyles( $('body#top').prev('head') );
  1940. assembleUIElements();
  1941. assembleMenus();
  1942. if ( window.self === window.top ) {
  1943. appendNewIndex($('body#top'));
  1944. }
  1945. $main_content.find('tr').append( $sidebar_wrapper, $content_pane );
  1946. $('body#top').empty().append($main_content);
  1947. }
  1948. buildUI();
  1949.  
  1950. const $body = $('body#top');
  1951. const $iframe_body = $('body#iframe_body');
  1952. const $iFrame_head = $iframe_body.prev('head');
  1953. const $dir_list_row = $dir_list_body.find('> tr');
  1954. const $audio_files = $dir_list_body.find('.audio');
  1955. const $font_files = $dir_list_body.find('.font');
  1956. const $image_files = $dir_list_body.find('.image');
  1957. const $media_files = $dir_list_body.find('.media');
  1958. const $video_files = $dir_list_body.find('.video');
  1959. function $selected_file() { return $dir_list_body.find('.selected'); }
  1960. function $playing_file() { return $dir_list_body.find('.playing'); }
  1961.  
  1962. // UI Setup: build UI and add body classes and other initial settings
  1963. function setupUIprefs() {
  1964. for ( let key in $settings ) {
  1965. if ( getQuery(key) === 'true' ) {
  1966. $body.addClass(key);
  1967. } else { // non-boolean settings and others
  1968. if ( key === 'theme' ) { $body.addClass( 'theme_'+ getQuery('theme') ); }
  1969. if ( key === 'sort_by' ) {
  1970. $body.addClass( 'sort_by_'+ getQuery(key) );
  1971. $('#theader').find('th[id="sort_by_'+ getQuery(key) +'"]').addClass('up');
  1972. }
  1973. if ( key === 'default_text_view' ) {
  1974. if ( getQuery(key) === 'source_text' ) { $body.addClass('source_text'); } else { $body.addClass('preview_text'); }
  1975. }
  1976. if ( getQuery('toggle_sidebar') === 'true' ) { $body.addClass( 'has_hidden_sidebar' ); }
  1977. }
  1978. }
  1979. if ( navigator.userAgent.indexOf('Chrome') > 0 ) { $body.addClass('is_chrome'); }
  1980. if ( navigator.userAgent.indexOf('Firefox') > 0 ) { $body.addClass('is_gecko'); }
  1981. if ( $audio_files.length > 0 ) { $body.add($dir_list).addClass('has_audio has_media'); }
  1982. if ( $font_files.length > 0 ) { $body.addClass('has_fonts'); }
  1983. if ( $image_files.length > 0 ) { $body.addClass('has_images'); }
  1984. if ( $video_files.length > 0 ) { $body.add($dir_list).addClass('has_video has_media'); }
  1985. // UI Setup: show invisibles
  1986. if ( navigator.platform.indexOf('Win') > -1 || $location.indexOf('file:') < 0 ) {
  1987. $inv_checkbox.hide();
  1988. } else if ( $body.hasClass('show_invisibles') ) {
  1989. $inv_checkbox.find('input').attr('checked','checked');
  1990. }
  1991. }
  1992. // Get DOMTokenList of body classes
  1993. function getClassList(id) { return document.getElementById(id).classList; }
  1994. //
  1995. function setUpUI() {
  1996. if ( window.self === window.top) {
  1997. setupUIprefs();
  1998. initMedia();
  1999. autoSelectFile();
  2000. autoLoadCoverArt();
  2001. setContentTitle();
  2002. setContentHeight();
  2003. } else {
  2004. setUpIframeUI( getQuery('enable_text_editing') );
  2005. }
  2006. }
  2007. setUpUI();
  2008.  
  2009. // SET UI TO DEFAULT SETTINGS: remove queries;
  2010. function defaultSettings() {
  2011. let $location = window.location.href;
  2012. $location = $location.slice(0,$location.lastIndexOf('?'));
  2013. window.location.assign($location);
  2014. }
  2015. $default_settings.on('click', function() {
  2016. if (window.confirm( 'Are you sure you want to reset all your temporary UI settings to the defaults in your user_settings?\nThis action cannot be undone.' ) ) {
  2017. defaultSettings();
  2018. }
  2019. });
  2020. // EXPORT SETTINGS
  2021. function saveSettings(filename, data) {
  2022. const blob = new Blob([data], {type: 'text/html'});
  2023. const elem = window.document.createElement('a');
  2024. elem.href = window.URL.createObjectURL(blob);
  2025. elem.download = filename;
  2026. document.body.appendChild(elem);
  2027. elem.click();
  2028. document.body.removeChild(elem);
  2029. URL.revokeObjectURL(blob);
  2030. }
  2031. $export_settings.on('click','a',function(e) {
  2032. e.preventDefault();
  2033. const $settings_string = ( JSON.stringify($settings,null,'\t'));
  2034. saveSettings('settings.txt',$settings_string);
  2035. });
  2036.  
  2037. // Click Menu Link (with warning)
  2038. function setLocation(link) { window.location = link; }
  2039. $('#parent_dir_menu,#parents_dir_menu + .menu,#shortcuts').on('click','a',function(e) {
  2040. e.preventDefault();
  2041. if ( $(this).attr('href').indexOf('file://') > -1 && $protocol !== 'file:' ) {
  2042. $body.addClass('has_warning').find('#warnings').addClass('local');
  2043. } else {
  2044. showWarning( 'setLocation', thisLink($(this)) );
  2045. }
  2046. });
  2047. // Show Menus
  2048. function showMenus(el) {
  2049. let $position = $(el).position();
  2050. $(el).find('> ul').css({'top':$position.top + $(el).innerHeight() + 'px'}).toggle().parent('td').siblings('td').find('.menu').hide();
  2051. }
  2052. $parents_dir_menu.add($shortcuts_menu).parent('td').on('click',function(e) {
  2053. e.stopPropagation();
  2054. showMenus(this);
  2055. });
  2056. // Hide Menus
  2057. $(document).on('click', function() { $('.menu').hide(); });
  2058.  
  2059. // Toggle UI Preferences
  2060. function toggleUIpref(prefID) {
  2061. let prefClass = prefID;
  2062. if ( prefID === 'split_view' ) { sendMessage('iframe','split_view'); }
  2063. if ( prefID.startsWith('sort_by') ) { $body.removeClass('sort_by_default sort_by_name sort_by_size sort_by_date sort_by_kind sort_by_ext'); }
  2064. if ( prefID.startsWith('theme') ) { prefClass = 'theme_dark theme_light'; }
  2065. if ( prefID.endsWith('text') && !$body.hasClass(prefID) ) {
  2066. sendMessage('iframe','default_text_view');
  2067. $body.toggleClass('preview_text source_text');
  2068. }
  2069. toggleQuery(prefID);
  2070. $body.toggleClass(prefClass);
  2071. setContentHeight();
  2072. if ( prefID === 'enable_text_editing' ) { $('.selected.text, .selected.markdown').click(); }
  2073. // $('#shortcuts').hide(); // don't hide menus after click?
  2074. }
  2075. // Click Toggle UI Pref elements
  2076. $('.toggle_UI_pref').on('click',function(e) {
  2077. if ( !$(this).is('input') ) {
  2078. e.preventDefault(); // allow checkboxes to be checked
  2079. }
  2080. toggleUIpref( $(this).attr('id') );
  2081. });
  2082.  
  2083. // Toggle Sidebar
  2084. function toggleSidebar() {
  2085. $body.toggleClass('has_hidden_sidebar');
  2086. if ( $body.hasClass('has_hidden_sidebar') ) { setQuery('toggle_sidebar','true'); } else { removeQuery('toggle_sidebar'); }
  2087. setContentHeight();
  2088. scrollThis('tbody','selected',true); // true = instant scroll
  2089. }
  2090. $toggle_sidebar.on('click', function() {
  2091. toggleSidebar();
  2092. });
  2093.  
  2094. // RESIZE Sidebar/Content Pane
  2095. function resizeSidebar(f) {
  2096. f.stopPropagation();
  2097. const $startX = f.pageX;
  2098. let $window_width = window.innerWidth;
  2099. let $sidebar_width = $sidebar_wrapper.width();
  2100. $('#overlay').css({'display':'block','z-index':'1'});
  2101. $('#handle').css({'z-index':'9999'});
  2102. $body.css({'-moz-user-select':'none','user-select':'none'});
  2103.  
  2104. $(document).on('mousemove',function(e) {
  2105. e.stopPropagation();
  2106. e.preventDefault();
  2107. const $deltaX = e.pageX - $startX;
  2108. if ( e.pageX > 230 && e.pageX < $window_width - 200 ) {
  2109. $sidebar_wrapper.css({'width':$sidebar_width + $deltaX + 'px'});
  2110. $content_pane.css({'width':($window_width - $sidebar_width) - $deltaX + 'px'});
  2111. }
  2112. setContentHeight();
  2113. });
  2114. $(document).on('mouseup',function() {
  2115. $('#overlay').css({'display':'none','z-index':'unset'});
  2116. $('#handle').css({'z-index':'unset'});
  2117. $body.css({'-moz-user-select':'unset','user-select':'unset'});
  2118. $(document).off('mousemove');
  2119. $sidebar_width = $sidebar_wrapper.width();
  2120. setQuery('width',$sidebar_width);
  2121. });
  2122. }
  2123. $handle.on('mousedown', function(f) {
  2124. f.stopPropagation();
  2125. resizeSidebar(f);
  2126. });
  2127.  
  2128. // ***** BASIC UI FUNCTIONS ***** //
  2129. // Get dir_list item row
  2130. function thisRow(x) {
  2131. return $(x).closest('#dir_list > tbody > tr').length > 0 ? $(x).closest('#dir_list > tbody > tr') : $(x).closest('#dir_list > li');
  2132. }
  2133. function thisID(x) {
  2134. return thisRow(x).attr('id');
  2135. }
  2136. // Get row link
  2137. function thisLink(x) {
  2138. return $(x).find('a').length > 0 ? $(x).find('a').attr('href') : $(x).attr('href');
  2139. }
  2140. // Get row name
  2141. function thisText(x) {
  2142. return $(x).find('a span').length > 0 ? decodeURIComponentSafe( $(x).find('a span').text().toLocaleLowerCase() ) : decodeURIComponentSafe( $(x).text().toLocaleLowerCase() );
  2143. }
  2144. // get row text
  2145. function thisExt(x) {
  2146. let $this_name = thisText(x);
  2147. return $this_name.endsWith('app/') ? 'app' : $this_name.endsWith('/') ? '/' : $this_name.lastIndexOf('.') === -1 ? undefined : $this_name.toLocaleLowerCase().slice( $this_name.lastIndexOf('.') + 1 );
  2148. }
  2149. function getElById(id) {
  2150. return $(document.getElementById(id) );
  2151. }
  2152.  
  2153. // SET CONTENT HEIGHT
  2154. function setContentHeight() {
  2155. // accommodate multi-line title names in preview pane
  2156. if ( $title.outerWidth() > ( $('#content_title').innerWidth() - 2 * $('#title_buttons_left').outerWidth() ) ) {
  2157. $title.css({'margin-top':'2em'});
  2158. } else {
  2159. $title.css({'margin-top':'0'});
  2160. }
  2161. let $window_height = window.innerHeight;
  2162. let $content_header_height = $content_header.outerHeight();
  2163.  
  2164. $sidebar.add($main_content).add($content_pane).css({'height':$window_height });
  2165. $dir_list.css({'height':$window_height - $('#sidebar_header').outerHeight(), 'max-height':$window_height - $('#sidebar_header').outerHeight() });
  2166. $dir_list_body.css({'top': $dir_list_head.outerHeight(),'bottom': $dir_list.find('tfoot').outerHeight(),'height': $dir_list.innerHeight() - $dir_list_head.outerHeight() - $dir_list.find('tfoot').outerHeight(),'max-height': $dir_list.innerHeight() - $dir_list_head.outerHeight() - $dir_list.find('tfoot').outerHeight() });
  2167. $('#iframe_body').find('#tbody').css({'top':$('#iframe_body').find('#thead').height() + 1 +'px'});
  2168.  
  2169. $prev_track.add($next_track).add($close_audio).css({'height':$('#audio').height() }); // set height of audio controls
  2170. $content_container.css({'top':$content_header_height +'px' });
  2171. }
  2172. window.addEventListener('resize', setContentHeight );
  2173.  
  2174. // SET CONTENT TITLE
  2175. function setContentTitle() {
  2176. let $title_text = '';
  2177. if ( $playing_file().hasClass('audio') ) { // set audio player title
  2178. $content_audio_title.find('td').empty().text( $playing_file().find('td.name a').text() );
  2179. }
  2180. if ( $content_pane.hasClass('has_grid') ) { // set main title for other content
  2181. $title_text = $('#parents_dir_menu').find('> div').html().split('<wbr>').reverse()[1];
  2182. } else if ( $('#toggle_info').hasClass('selected') ) {
  2183. $title_text = 'Source of: '+ $current_dir_path;
  2184. } else if ( $body.hasClass('has_text') ) {
  2185. $title_text = 'Text Editor';
  2186. } else if ( $selected_file().hasClass('audio') ) {
  2187. return;
  2188. } else if ( !$selected_file().hasClass('audio') || $content_pane.hasClass('has_hidden_text') ) {
  2189. $title_text = $selected_file().not('.audio').find('td.name a span').text(); // Assemble title
  2190. }
  2191. if ( $content_pane.hasClass('has_image') ) {
  2192. setDimensions($('.selected.image'));
  2193. } else {
  2194. $title.attr('data-after',''); // remove image dimensions
  2195. }
  2196. $title.empty().html($title_text);
  2197. if ( $title.outerWidth() > ( $('#content_title').innerWidth() - 2 * $('#title_buttons_left').outerWidth() ) ) { $title.css({'margin-top':'2em'}); } else { $title.css({'margin-top':'0'}); }
  2198. }
  2199. // Get Image Dimensions
  2200. function getDimensions(link, callback) {
  2201. let img = new Image();
  2202. img.src = link;
  2203. img.onload = function() { callback( this.width, this.height ); };
  2204. }
  2205. function setDimensions(row) {
  2206. getDimensions( thisLink(row), function( width, height ) {
  2207. $title.attr('data-after',' (' + width + 'px × ' + height + 'px)');
  2208. });
  2209. }
  2210. // Scroll Selected Items
  2211. function scrollThis(elID,className,bool) {
  2212. let $behavior = 'smooth';
  2213. if ( bool !== undefined ) { $behavior = 'instant'; }
  2214. let $block = ( navigator.userAgent.indexOf('Firefox') > -1 ? 'start' : 'nearest' );
  2215. if ( document.getElementsByClassName(className).length > 0 ) {
  2216. document.getElementById(elID).getElementsByClassName(className)[0].scrollIntoView({ behavior:$behavior, block:$block, inline:'nearest' });
  2217. }
  2218. setContentHeight();
  2219. }
  2220.  
  2221. // ***** SORTING ***** //
  2222. function sortIndex(els,id,sortDirection) { // id = sort type
  2223. let sorted = [];
  2224.  
  2225. const newSort = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); // needs to be above makeNewIndex()
  2226. sorted = els.removeClass('sorted').sort((a, b) => {
  2227. let aName = $(a).find('td.name').data('name');
  2228. let bName = $(b).find('td.name').data('name');
  2229. let aData = $(a).find('td[data-'+ id +']').data(id);
  2230. let bData = $(b).find('td[data-'+ id +']').data(id);
  2231. if ( sortDirection === 1 ) { // sort by detail data value, then by name (i.e., if data values are the same, sort by name)
  2232. if ( newSort.compare(aData, bData) === 0 ) {
  2233. return newSort.compare(aName, bName);
  2234. } else {
  2235. return newSort.compare(aData, bData);
  2236. }
  2237. } else { // reverse sort
  2238. if ( newSort.compare(bData, aData) === 0 ) {
  2239. return newSort.compare(bName, aName);
  2240. } else {
  2241. return newSort.compare(bData, aData);
  2242. }
  2243. }
  2244. });
  2245. sorted = sorted.sort((a, b) => { // add sorted style (rules between different rows)
  2246. if ( id === 'kind' || id === 'ext' && sortDirection === 1 ) {
  2247. if ( $(a).find('td[data-'+ id +']').data(id) !== $(b).find('td[data-'+ id +']').data(id) ) { $(a).addClass('sorted'); }
  2248. }
  2249. if ( id === 'kind' || id === 'ext' && sortDirection === -1 ) {
  2250. if ( $(b).find('td[data-'+ id +']').data(id) !== $(a).find('td[data-'+ id +']').data(id) ) { $(a).addClass('sorted'); }
  2251. }
  2252.  
  2253. });
  2254. return sorted;
  2255. }
  2256. // Sort the Dir List on click
  2257. function sortDirList(rows, id, sortDirection) {
  2258. const $sort_all = rows;
  2259. const $sort_dirs = $sort_all.filter('.dir:not(.app)');
  2260. const $sort_files = $sort_all.filter('.file,.app');
  2261. const $sort_id = id.slice(8);// === 'default' ? 'name' : id;
  2262.  
  2263. const $sorted_dirs = sortIndex($sort_dirs, $sort_id, sortDirection);
  2264. const $sorted_files = sortIndex( $sort_files, $sort_id, sortDirection );
  2265. const $sorted_all = sortIndex( $sort_all, $sort_id, sortDirection );
  2266. let $sorted = [];
  2267.  
  2268. if ( $sort_id === 'default' || ( $sort_id !== 'name' && $settings.dirs_on_top === true )) {
  2269. if (sortDirection === 1) {
  2270. $sorted = $.merge($sorted_dirs,$sorted_files);
  2271. } else {
  2272. $sorted = $.merge($sorted_files,$sorted_dirs);
  2273. }
  2274. } else {
  2275. $sorted = $sorted_all;
  2276. }
  2277. return $sorted;
  2278. }
  2279. // Sort on click
  2280. function clickSort(el) {
  2281. const id = el.attr('id'); // id from th sort item = 'sort_by_xxx'
  2282. var sortDirection = ( el.hasClass('up') ? -1 : 1 ); // Only reverse sort order after clicking a sort column once.
  2283. // SORT EM!
  2284. const $sorted_index = sortDirList( $dir_list_row, id, sortDirection );
  2285. $dir_list_body.append($sorted_index);
  2286. $('th[id="'+ id +'"]').toggleClass('up').siblings().removeClass('up');
  2287. if ( $content_grid.hasClass('has_font_grid') ) { $('#show_font_grid').click(); }
  2288. if ( $content_grid.hasClass('has_image_grid') ) { $('#show_image_grid').click(); }
  2289. if ( $content_grid.hasClass('has_grid') ) { $('#grid_btn').click(); }
  2290. setContentHeight();
  2291. }
  2292. // Sort on clicking dir_list sort item
  2293. $('#theader').on('click','th.sorting:not(.disabled)',function(e) {
  2294. e.preventDefault();
  2295. e.stopPropagation();
  2296. clickSort( $(this) );
  2297. });
  2298. // Sort Menu: click the list header that contains the selected menu text.
  2299. $('#sort_menu').on('click','li:not(.disabled)',function(e) {
  2300. e.preventDefault();
  2301. e.stopPropagation();
  2302. $('#theader th[id="sort_by_'+ $(this).attr('id') +'"]').click(); // click corresponding dir_list sort item
  2303. });
  2304. // ***** END SORTING ***** //
  2305. // ***** END BASIC UI FUNCTIONS ***** //
  2306.  
  2307. // ***** CONTENT PANE ***** //
  2308. // SELECT ROW on click and set classes for $content_pane
  2309. function selectThis(row) {
  2310. $('#toggle_info').removeClass('selected');
  2311. const $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();
  2312. if ( $content_pane.hasClass('has_grid') ) { // Select corresponding grid item
  2313. row.addClass('selected').siblings().removeClass('selected hovered');
  2314. $grid_selected.addClass('selected').siblings().removeClass('selected');
  2315. } else { // remove classes from rows, but leave .audio with playing
  2316. if ( row.attr('id') === 'toggle_info' ) {
  2317. row.toggleClass('selected loaded');
  2318. $dir_list.find('#tbody .selected').removeClass('selected');
  2319. } else {
  2320. row.addClass('selected').siblings().removeClass('selected hovered');
  2321. }
  2322. }
  2323. }
  2324.  
  2325. //***** SHOW CONTENT *****//
  2326. function showAudio(rowID) {
  2327. let row = getElById(rowID);
  2328. if ( $content_video.hasClass('has_content') ) { $title.empty(); }
  2329. closeMedia('video');
  2330. if ( row.hasClass('audio') ) { row.addClass('playing selected').siblings('.media').removeClass('playing selected'); }
  2331. $audio_player.attr('src', thisLink( row ) );
  2332. $content_pane.addClass('has_audio');
  2333. $content_audio_title.find('td').empty().text( $playing_file().find('td.name a').text() );
  2334. setContentHeight();
  2335. }
  2336.  
  2337. // showTextEditor();
  2338.  
  2339. // Show Grid
  2340. function showGrid() {
  2341. $content_pane.removeClass('has_hidden_grid').addClass('has_grid');
  2342. if ( $body.hasClass('has_text') ) { $body.toggleClass('has_text has_hidden_text'); }
  2343. closeMedia('video');
  2344. setContentTitle();
  2345. setContentHeight();
  2346. selectThis($selected_file());
  2347. }
  2348. // Show Hidden Editor or Grid
  2349. function showHiddenEditorOrGrid() {
  2350. if ( $content_pane.hasClass('has_hidden_grid') && !$body.hasClass('has_hidden_text') || $content_pane.hasClass('has_hidden_grid') && $body.hasClass('has_hidden_text') ) {
  2351. $content_pane.toggleClass('has_grid has_hidden_grid'); // show grid preferentially
  2352. } else if ( !$content_pane.hasClass('has_hidden_grid') && $body.hasClass('has_hidden_text') ) {
  2353. $body.toggleClass('has_text has_hidden_text');
  2354. }
  2355. }
  2356. // Hide Editor or Grid
  2357. function hideEditorOrGrid() {
  2358. if ( $body.hasClass('has_text') ) { $body.toggleClass('has_text has_hidden_text'); }
  2359. if ( $content_pane.hasClass('has_grid') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  2360. }
  2361. // showIndexSource();
  2362.  
  2363. // Show Font (and create font items)
  2364. function showFont(row,bool,sheet) { // bool = true if for show font grid, sheet defined at fontGridItems()
  2365. let fontStyles = $font_styles.sheet;
  2366. const $font_family = thisText(row);
  2367. const $font_url = thisLink(row);
  2368. const $font_grid_item_el = $('<div class="font_grid_item"></div>');
  2369. if ( bool === false ) {
  2370. if ( fontStyles.cssRules.length > 0 ) { fontStyles.deleteRule(0); } // delete previous @font-face rule
  2371. fontStyles.insertRule('@font-face { font-family: "'+ $font_family +'"; src: url("'+ $font_url +'"); }');
  2372. $content_font.css({ 'font-family':'"'+ $font_family +'"' }); // set content font style
  2373. } else {
  2374. let grid_item = $font_grid_item_el.clone();
  2375. const $display_name = $font_family;
  2376. sheet.insertRule('@font-face { font-family: "'+ $font_family +'"; src: url("'+ $font_url +'"); }');
  2377. grid_item.append('<p>'+ $display_name +'</p><h2 style=\'font-family: "'+ $font_family +'"\'; ><a href="'+ $font_url +'">'+ $display_name.slice(0,$font_family.lastIndexOf('.')) +'</a></h2>' );
  2378. return grid_item;
  2379. }
  2380. }
  2381. // Set Content Pane classes
  2382. function setContentClasses(content_el) {
  2383. if ( content_el === 'audio' ) {
  2384. $content_pane.addClass('has_audio');
  2385. } else {
  2386. hideEditorOrGrid();
  2387. $content_pane.addClass( 'has_'+ content_el ); // remove classes, excluding grid and text classes; add content class
  2388. }
  2389. }
  2390. // Set Content Element Sources and row classes
  2391. function setContentSources(row,link,kind,content_el) {
  2392. switch(kind) {
  2393. case 'audio':
  2394. $audio_player.attr('src',link);
  2395. break;
  2396. case 'font':
  2397. showFont(row,false);
  2398. break;
  2399. case 'code':
  2400. case 'markdown':
  2401. case 'text':
  2402. link = thisLink(row) +'?split_view='+ getQuery('split_view') +'&default_text_view='+ getQuery('default_text_view');
  2403. if ( !$body.hasClass('enable_text_editing') ) { link += '&enable_text_editing=false'; } else { link += '&enable_text_editing=true'; }
  2404. break;
  2405. case 'dir':
  2406. link = link + '?sort_by=' + getQuery('sort_by') +'&show_numbers='+ getQuery('show_numbers');
  2407. break;
  2408. case 'info':
  2409. link = $location + '?sort_by=' + getQuery('sort_by') +'&show_numbers='+ getQuery('show_numbers') +'&view_source=true';
  2410. break;
  2411. case 'pdf':
  2412. link = link + '?#view=fitB&scrollbar=1&toolbar=1&navpanes=1';
  2413. break;
  2414. }
  2415. if ( kind === 'video' ) { row.addClass('playing'); } else { row.addClass('loaded'); }
  2416. // fix for pdfs not being loaded after setting src attribute (hide and then show)
  2417. if ( kind === 'pdf' ) {
  2418. $content_pdf.hide().addClass('has_content').attr('src','').attr('src',link).show().removeAttr('srcdoc');
  2419. } else {
  2420. $('#content_'+ content_el).addClass('has_content').attr('src',link).removeAttr('srcdoc');
  2421. }
  2422. }
  2423. // Show Content
  2424. function showContent(row) { // show any content excluding grids on row click
  2425. if ( row.length === -1 ) { return; } // needed for left/right arrow nav when there are no valid items (i.e. images or fonts)
  2426. let rowID = row.attr('id');
  2427. let kind = row.attr('data-kind');
  2428. let link = thisLink(row);
  2429. let content_el = ( row.hasClass('ignore') && $body.hasClass('ignore_ignored_files') ? 'ignored' : !['audio','font','image','pdf','video'].includes(kind) || kind === 'dir' || kind === 'info' ? 'iframe' : kind ); // load dirs and any other kind of content except audio, font, image, pdf, or video into iframe
  2430. if ( row.hasClass('audio') ) {
  2431. showAudio( rowID );
  2432. } else {
  2433. closeOtherContent(row);
  2434. setContentClasses(content_el);
  2435. setContentSources(row,link,kind,content_el);
  2436. setContentTitle();
  2437. setContentHeight();
  2438. row.siblings().removeClass('loaded');
  2439. }
  2440. }
  2441.  
  2442. //***** CLOSE CONTENT (Close button or Cmd/Ctrl + W) *****//
  2443. // Close Audio/Video
  2444. function closeMedia(type) { // type === audio || video
  2445. let $mediaEl = ( type === 'audio' ? $audio_player : $content_video );
  2446. $mediaEl.trigger('pause').attr('src','');
  2447. $content_pane.removeClass('has_'+ type);
  2448. $('.media.playing, .media.loaded').removeClass('playing loaded');
  2449. if ( type === 'audio' ) {
  2450. $('#content_audio_title td').empty();
  2451. }
  2452. if ( type === 'video' ) {
  2453. $('#content_video').removeClass('has_content');
  2454. }
  2455. setContentHeight();
  2456. }
  2457. // Close Audio button click
  2458. $close_audio.on('click',function(e) {
  2459. e.stopPropagation();
  2460. closeMedia('audio');
  2461. $('.audio.selected').removeClass('selected');
  2462. if ( $('.loaded').length === 1 ) { $('.loaded').addClass('selected'); }
  2463. });
  2464. // Close Text Editor
  2465. function closeTextEditor() {
  2466. if ( $body.hasClass('edited') ) {
  2467. showWarning('closeContent');
  2468. } else {
  2469. $body.removeClass('has_text');
  2470. if ( $content_pane.hasClass('has_hidden_grid') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  2471. }
  2472. }
  2473. // Close Grid
  2474. function closeGrid() {
  2475. $content_pane.removeClass('has_grid');
  2476. $content_grid.empty().removeClass().attr('style','').find('.image_grid_item, img').attr('style','');
  2477. if ( $body.hasClass('has_hidden_text') ) { $body.toggleClass('has_text has_hidden_text'); }
  2478. }
  2479. // Close index source preview
  2480. function closeIndexSource() {
  2481. $('#toggle_info').removeClass('selected loaded');
  2482. $content_pane.removeClass('has_iframe').find('#content_iframe').removeClass('has_content').removeAttr('src');
  2483. if ( $('.loaded:not(.audio)').length === 1 || $('.playing').length > 0 ) {
  2484. $('.loaded:not(.audio)').click();
  2485. $('.playing').addClass('selected');
  2486. }
  2487. }
  2488. // Close .content elements (not #content_audio, #content_text, or #content_grid) when opening new content
  2489. function closeOtherContent(row) {
  2490. // close audio/video when opening video/audio
  2491. if ( row !== undefined ) {
  2492. if ( row.hasClass('video') ) {
  2493. closeMedia('audio');
  2494. } else if ( row.hasClass('audio') ) {
  2495. closeMedia('video');
  2496. }
  2497. }
  2498. // close other content
  2499. if ( $('.content.has_content').length || $content_pane.hasClass('has_ignored') ) {
  2500. let $contentID = $('.content.has_content').attr('id');
  2501. switch($contentID) { // specific cases
  2502. case 'content_font':
  2503. $content_font.css({'font-family':''});
  2504. break;
  2505. case 'content_image':
  2506. $content_pane.removeClass('has_zoom_image');
  2507. $content_image.attr('style',''); // reset image: comment out to retain image scale after loading other content
  2508. break;
  2509. case 'content_video':
  2510. closeMedia('video');
  2511. break;
  2512. }
  2513. // remove content classes and source attribute
  2514. $content_pane.removeClass('has_font has_image has_zoom_image has_pdf has_video has_iframe has_dir has_ignored').find('.content').removeClass('has_content').removeAttr('src'); ;
  2515. showHiddenEditorOrGrid();
  2516. }
  2517. }
  2518. // Close button: close any visible content
  2519. function closeContent() {
  2520. if ( $('#toggle_info').hasClass('selected') ) {
  2521. closeIndexSource();
  2522. } else if ( $body.hasClass('has_text') ) {
  2523. closeTextEditor();
  2524. } else if ( $content_pane.hasClass('has_grid') ) { // close grid (i.e., don't just hide)
  2525. closeGrid();
  2526. } else if ( ['has_font','has_image','has_zoom_image','has_pdf','has_video','has_iframe','has_dir','has_ignored'].some( c => $content_pane.attr('class').split(' ').indexOf( c ) >= 0 ) && !$body.hasClass('iframe_edited') ) {
  2527. $('.selected:not(.audio)').removeClass('selected');
  2528. $('.loaded').removeClass('loaded');
  2529. closeOtherContent();
  2530. } else if ( $body.hasClass('iframe_edited') ) { // warn if iframe edited
  2531. showWarning('closeContent');
  2532. } else if ( $content_pane.hasClass('has_audio') ) { // close audio last of all
  2533. closeMedia('audio');
  2534. $('.selected.audio').removeClass('selected');
  2535. }
  2536. setContentTitle();
  2537. setContentHeight();
  2538. }
  2539. $('#close_btn').on('click', function(e) {
  2540. e.preventDefault();
  2541. closeContent(); // close content unless body.edited or body.iframe_edited
  2542. });
  2543.  
  2544. //***** RESET CONTENT (Reset button or Cmd/Ctrl + R) *****//
  2545. function resetContent() {
  2546. if ( $content_pane.attr('class') === '' ) { window.location = window.location.href; } // reload page
  2547. if ( $content_pane.hasClass('has_audio') ) { $audio_player.prop('currentTime', 0).trigger('pause'); }
  2548. if ( $content_pane.hasClass('has_grid') ) { $grid_btn.click(); }
  2549. if ( $content_pane.hasClass('has_font') ) { $content_font.css({'font-size':'1em'});}
  2550. if ( $content_pane.hasClass('has_image') ) { $content_pane.removeClass('has_zoom_image').find('#content_image').attr('style',''); }
  2551. if ( $content_pane.hasClass('has_video') ) { $content_video.prop('currentTime',0).trigger('pause'); }
  2552. if ( $content_pane.hasClass('has_iframe') || $content_pane.hasClass('has_dir') ) { $selected_file().find('a').click(); }
  2553. setContentHeight();
  2554. }
  2555. $('#reload_btn').on('click', function(e) {
  2556. e.preventDefault();
  2557. showWarning('resetContent');
  2558. });
  2559.  
  2560. //**********************//
  2561. //***** NAVIGATION *****//
  2562. function firstRowID(className) {
  2563. return ( $('#tbody').find(':visible:not(.unchecked)').filter(className).length ? $('#tbody').find('tr:visible:not(.unchecked)').filter(className).first().attr('id') : null );
  2564. }
  2565. function prevRowID(className) {
  2566. let row;
  2567. if ( ['.audio','.video','.media'].includes(className) ) { row = $playing_file(); } else { row = $selected_file(); }
  2568. return ( !row.length || !row.prevAll(':visible:not(.unchecked)').filter(className).length ) ? $('#tbody').find('tr').filter(className).last().attr('id') : row.prevAll(':visible:not(.unchecked)').filter(className).first().attr('id');
  2569. }
  2570. function nextRowID(className) { // if nothing selected, or if no next row with classname, return first row with classname, else return next row with classname
  2571. let row;
  2572. if ( ['.audio','.video','.media'].includes(className) ) { row = $playing_file(); } else { row = $selected_file(); }
  2573. return ( !row.length || !row.nextAll(':visible:not(.unchecked)').filter(className).length ) ? $('#tbody').find('tr').filter(className).first().attr('id') : row.nextAll(':visible:not(.unchecked)').filter(className).first().attr('id');
  2574. }
  2575. // function lastRowID(className) {
  2576. // return ( $('#tbody').find(':visible:not(.unchecked)').filter(className).length ? $('#tbody').find('tr:visible:not(.unchecked)').filter(className).last().attr('id') : null );
  2577. // }
  2578. function selectRowID(className,key) {
  2579. let id = '';
  2580. if ( key === 'ArrowUp' ) { id = prevRowID('.dir,.file'); }
  2581. if ( key === 'ArrowLeft' ) { id = prevRowID(className); }
  2582. if ( key === 'ArrowRight' ) { id = nextRowID(className); }
  2583. if ( key === 'ArrowDown' ) { id = nextRowID('.dir,.file'); }
  2584. return id;
  2585. }
  2586. // NAVIGATION: select GRID items by left/right arrow keys @ arrowNavigation();
  2587. function gridNavigation(key) {
  2588. let className = '';
  2589. if ( $content_grid.hasClass('has_font_grid') ) { className = '.font:not(.ignore)'; }
  2590. if ( $content_grid.hasClass('has_image_grid') ) { className = '.image:not(.ignore)'; }
  2591. if ( $content_grid.hasClass('has_grid') ) { className = '.font:not(.ignore),.image:not(.ignore)'; }
  2592. selectThis( getElById(selectRowID(className,key) ) );
  2593. }
  2594. // NAVIGATION: FONTS and IMAGES by prev/next buttons
  2595. $content_pane.on( 'click','#prev_btn, #next_btn', function(e) {
  2596. e.stopPropagation();
  2597. e.preventDefault();
  2598. let key = $(this).attr('id') === 'prev_btn' ? 'ArrowLeft' : 'ArrowRight';
  2599. clickRow( selectRowID('.font,.image',key));
  2600. });
  2601. // NAVIGATION: MEDIA tracks by arrow left/right @ leftRightArrowNavigation() & button click; // handle shuffle or loop play, as well as normal continuous playback after end of track with ArrowRight
  2602. function playPrevNextTrack(key) {
  2603. if ( $('.playing').length === 0 ) { // Arrow L/R selects first/last audio file if nothing selected
  2604. clickRow( selectRowID('.media',key) );
  2605. playMedia('play');
  2606. } else if ( $('.playing').length === 1 ) {
  2607. let mediaClass = ( $('.playing').hasClass('audio') ? '.audio' : '.video' );
  2608. // If shuffle play...
  2609. if ( $body.hasClass('shuffle_audio') ) {
  2610. let trackRowID = $audio_player.data('shufflelist').pop();
  2611. if ( trackRowID !== undefined ) { // if shuffle list is not empty...
  2612. showAudio( trackRowID ); // load audio...
  2613. playMedia('play'); // and play
  2614. } else if ( trackRowID === undefined ) { // if end of shufflelist...
  2615. if ( $body.hasClass('loop_audio') ) { // and if loop audio, update the shufflelist and play
  2616. updateShuffleList();
  2617. playMedia('play');
  2618. } else { // else load the first track
  2619. showAudio( firstRowID( mediaClass ));
  2620. }
  2621. }
  2622. // else if there is another media file selected, play it next
  2623. } else if ( $(mediaClass).filter('.selected').length === 1 && !$('.media.selected').hasClass('playing') ) {
  2624. showAudio( $('.media.selected').attr('id') );
  2625. playMedia('play');
  2626. } else {
  2627. if ( $body.hasClass('loop_audio') || nextRowID( mediaClass ) !== firstRowID( mediaClass ) ) {
  2628. showAudio( selectRowID( mediaClass,key ) );
  2629. playMedia('play');
  2630. } else {
  2631. showAudio( selectRowID( mediaClass,key ) );
  2632. }
  2633. }
  2634. }
  2635. }
  2636. // NAVIGATION: Audio by prev/next audio buttons
  2637. $('.prev_next_track_btn').on('click',function() {
  2638. let key = ( $(this).attr('id') === 'prev_track' ? 'ArrowLeft' : 'ArrowRight' );
  2639. playPrevNextTrack(key);
  2640. });
  2641. // NAVIGATION: Prev/Next Audio or Prev/Next File of same data-kind by arrow left/right
  2642. function leftRightArrowNavigation(className,key) {
  2643. if ( $('.selected').hasClass('audio') && $('.playing').length === 1 ) {
  2644. playPrevNextTrack(key);
  2645. } else if ( $('#toggle_info').hasClass('selected') ) { // if previewing directory source
  2646. if ( $('#tbody .loaded').length > 0 ) { clickRow( $('#tbody .loaded').attr('id') ); } else { clickRow( selectRowID('.dir,.file',key) ); }
  2647. } else {
  2648. if ( $('.selected').length === 0 ) { // else select first/last row
  2649. clickRow( selectRowID('.dir,.file',key) );
  2650. } else { // else select prev/next row of same data-kind
  2651. clickRow( selectRowID('.'+ $('.loaded,.video.playing').attr('data-kind'),key) );
  2652. }
  2653. if (className === 'video') { playMedia('play'); }
  2654. }
  2655. }
  2656. function upDownArrowNavigation(key) {
  2657. let row = $(document.getElementById(selectRowID('.dir,.file',key)));
  2658. if ( row.hasClass('audio') && $('.audio.playing').length === 1 ) { // just select audio file if another audio is playing
  2659. selectThis( row );
  2660. } else if ( $('#toggle_info').hasClass('selected') ) {
  2661. if ( $('#tbody .loaded').length > 0 ) { clickRow( $('#tbody .loaded').attr('id') ); } else { clickRow( selectRowID('.dir,.file',key) ); }
  2662. } else {
  2663. clickRow( selectRowID('.dir,.file',key) );
  2664. }
  2665. }
  2666. // NAVIGATE directory index items by arrow up/down or left/right keys, with/without grid @ indexNavigation()
  2667. function arrowNavigation(className,key) {
  2668. if ( key === 'ArrowUp' || key === 'ArrowDown' ) {
  2669. upDownArrowNavigation(key);
  2670. } else { // key === 'ArrowLeft" || key === 'ArrowRight'
  2671. if ( $content_pane.hasClass('has_grid') ) { // Grid navigation: L/R arrow selects images and fonts only
  2672. gridNavigation(key);
  2673. } else {
  2674. leftRightArrowNavigation(className,key);
  2675. }
  2676. }
  2677. if ( $content_pane.hasClass('has_grid') ) {
  2678. scrollThis('content_grid','selected');
  2679. } else {
  2680. scrollThis('tbody','selected');
  2681. }
  2682. }
  2683.  
  2684. // NAVIGATION: Main navigation by arrow keys
  2685. function indexNavigation(key) {
  2686. let className = ( $('.selected[data-kind]') === undefined ? $dir_list.find('tbody tr:visible').first().attr('data-kind') : $('.selected[data-kind]').attr('data-kind') );
  2687. arrowNavigation(className,key);
  2688. }
  2689.  
  2690. // NAVIGATE items by typed string
  2691. var str = '';
  2692. function timeoutID() {
  2693. return window.setTimeout( function() { str = ''; }, 1500 );
  2694. }
  2695. function alphaNav(e) { // Select Dir_List row by typed string; Todo: select next row by nearest letter
  2696. let timer = timeoutID();
  2697. if ( typeof timer === 'number' ) {
  2698. window.clearTimeout( timer );
  2699. timer = 0; // id
  2700. }
  2701. timeoutID();
  2702. str += e.key;
  2703. str = str.toLowerCase();
  2704. if ( $('#dir_list').find('td.name[data-name^="'+ str +'"]').length ) {
  2705. $('#dir_list').find('td.name[data-name^="'+ str +'"]').first().find('a').click();
  2706. scrollThis('tbody','selected');
  2707. // } else {
  2708. // null; // replace this with some sort of fuzzy match function? TBD
  2709. }
  2710. }
  2711. //***** END NAVIGATION *****//
  2712.  
  2713. //***** CLICK TO SELECT/SHOW CONTENT *****//
  2714.  
  2715. // CLICK element by id
  2716. function clickThis(id) {
  2717. let el = $(document.getElementById(id));
  2718. if ( el.find('a').length > 0 ) { el.find('a').click(); } else { el.click(); }
  2719. }
  2720. // CLICK Row
  2721. function clickRow(id) {
  2722. let row = getElById(id);
  2723. selectThis(row);
  2724. showContent(row);
  2725. }
  2726. $body.find('#tbody').on('click','tr', function(e) {
  2727. e.preventDefault();
  2728. e.stopPropagation();
  2729. if ( $(this).hasClass('playing') ) {
  2730. playPauseMedia();
  2731. } else {
  2732. showWarning( 'clickRow', $(this).attr('id') );
  2733. }
  2734. });
  2735. // Show current dir source in $content_iframe
  2736. $('#toggle_info').on('click',function(e) {
  2737. e.stopPropagation();
  2738. if ( $(this).hasClass('selected') ) {
  2739. $('#close_btn').click();
  2740. } else {
  2741. showWarning( 'clickRow', $(this).attr('id') );
  2742. }
  2743. });
  2744.  
  2745. // DOUBLE-CLICK Row to open directory
  2746. function doubleClickRow(id) { // row.dir only
  2747. let row = getElById(id);
  2748. let $query_str = decodeURIComponentSafe(window.location.search);
  2749. if ( $query_str === '' ) { $query_str = '?'; }
  2750. const $current_index = row.prevAll('.dir:visible').length;
  2751. if ( $query_str.indexOf('history') !== -1 ) {
  2752. $query_str = $query_str.replace(/&selected=\d+/,'').replace(/&history=/,'&history='+ $current_index +'+');
  2753. } else {
  2754. $query_str = $query_str.replace(/&selected=\d+/,'') + '&history='+ $current_index;
  2755. }
  2756. window.location = row.find('a').attr('href') + $query_str;
  2757. }
  2758. $('#tbody').find('tr.dir').on('dblclick', function(e) {
  2759. e.preventDefault();
  2760. e.stopPropagation();
  2761. showWarning( 'doubleClickRow', $(this).attr('id') );
  2762. });
  2763.  
  2764. // CLICK grid item
  2765. function gridItemClick(e,el) {
  2766. e.preventDefault();
  2767. clickThis( thisID($dir_list.find('a[href*="'+ thisLink(el) +'"]') ) );
  2768. }
  2769. $content_grid.on('click','div', function(e) {
  2770. gridItemClick(e,$(this));
  2771. });
  2772.  
  2773. // HOVER Grid Item
  2774. $content_grid.on('mouseenter','> div:not(.selected)',function() {
  2775. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).addClass('hovered');
  2776. }).on('mouseleave','> div:not(.selected)',function() {
  2777. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).removeClass('hovered');
  2778. });
  2779. // HOVER Dir_list_row and highlight corresponding grid item
  2780. $dir_list_row.hover(function() {
  2781. if ( $content_grid.is(':visible') ) {
  2782. $content_grid.find('[href*="'+ thisLink(this) +'"]').closest('div').addClass('hovered');
  2783. }
  2784. }, function() {
  2785. if ( $content_grid.is(':visible') ) {
  2786. $content_grid.find('.hovered').removeClass('hovered');
  2787. }
  2788. });
  2789.  
  2790. //***** AUTOLOAD CONTENT: index files or files from the file shortcut list *****//
  2791. function autoSelectFile() {
  2792. let $query_prefs = getQueryPrefs();
  2793. let $UI_pref_selected = ( $query_prefs.get('selected') === null ? '' : $query_prefs.get('selected') );
  2794. if ( getQuery('file') !== undefined && getQuery('file').length > 0 && window.top == window.self ) { // load individual files
  2795. clickRow( $dir_list.find('a[href*="'+ getQuery('file') +'"]').closest('tr').attr('id') );
  2796. removeQuery('file');
  2797. } else if ( $UI_pref_selected !== '' && !$body.hasClass('has_media') ) {
  2798. clickRow( $dir_list.find('tr.dir:visible').eq($UI_pref_selected).attr('id') );
  2799. } else if ( $body.hasClass('autoload_index_files') && $dir_list.find( 'a[href*="/index."]').length > 0 ) { // else load index file
  2800. clickRow( $dir_list.find('a[href*="/index."]').closest('tr').attr('id') );
  2801. } else { // else select first non-media item
  2802. // clickRow( $dir_list.find('tbody tr:not(.media):not(.invisible)').first() );
  2803. }
  2804. if ( $body.hasClass('autoload_media') && $body.hasClass('has_media') ) { // else if audio and images, load cover art
  2805. clickRow( firstRowID('.audio,.video') );
  2806. if ( $playing_file() ) { scrollThis('tbody','playing'); }
  2807. }
  2808. if ( $selected_file() ) { scrollThis('tbody','selected'); }
  2809. }
  2810.  
  2811. // Autoload Cover Art
  2812. function getImageNames() {
  2813. let image_names = [];
  2814. $image_files.each(function() {
  2815. let name = $(this).find('td.name').attr('data-name');
  2816. name = name.slice(0,name.lastIndexOf('.'));
  2817. image_names.push( name );
  2818. });
  2819. return image_names;
  2820. }
  2821. function getCoverArtID() {
  2822. const cover_names = ['cover','front'];
  2823. const image_names = getImageNames();
  2824. for ( let cover_name of cover_names ) {
  2825. if ( image_names.includes(cover_name) ) { // file name = a cover name
  2826. return $image_files.eq( image_names.indexOf(cover_name) ).attr('id');
  2827. } else if ( image_names.some( name => name.startsWith(cover_name)) ) { // file name starts with a cover name
  2828. return $image_files.find('td.name[data-name^="'+ cover_name +'"]').closest('tr').attr('id');
  2829. } else if ( image_names.some( name => name.indexOf(cover_name) > 0 ) ) { // file name includes a cover name
  2830. return $image_files.find('td.name[data-name*="'+ cover_name +'"]').closest('tr').attr('id');
  2831. } else { // else use first image
  2832. return $image_files.first().attr('id');
  2833. }
  2834. }
  2835. }
  2836. function autoLoadCoverArt() { // autoload cover art for audio if dir contains audio and images, and audio is loaded and non-image content is not loaded
  2837. let bodyClasses = getClassList('top');
  2838. if ( bodyClasses.contains( 'has_audio','has_images','autoload_media' ) && !getElById( firstRowID('.media') ).hasClass('video') ) {
  2839. let $coverID = getCoverArtID();
  2840. if ( $coverID !== undefined ) {
  2841. let row = getElById($coverID);
  2842. showContent(row);
  2843. row.addClass('loaded');
  2844. $title.html(row.find('.name').attr('data-name'));
  2845. setDimensions($('.loaded.image'));
  2846. }
  2847. }
  2848. }
  2849.  
  2850. //***** KEYBOARD EVENTS *****//
  2851. $body.on('keydown',$dir_list,function(e) {
  2852. const $selected = $selected_file();
  2853.  
  2854. if ( $('#content_source').is(':focus') || $('#content_font div').is(':focus') && e.key !== 'Escape' ) {
  2855. return;
  2856. }
  2857. // Disable all keydown events except return and tab when warning is shown
  2858. if ( $('body').hasClass('has_warning') ) {
  2859. if (e.key !== 'Enter' && e.key !== 'Tab' && e.key !== 'Shift' ) {
  2860. e.preventDefault();
  2861. return false;
  2862. }
  2863. if ( e.key === 'Enter' ) {
  2864. e.preventDefault();
  2865. e.stopPropagation();
  2866. $('#warnings').find('button.focus').click();
  2867. }
  2868. if ( e.key === 'Tab' ) {
  2869. e.preventDefault();
  2870. e.stopPropagation();
  2871. if ( $('#warnings').find('button.focus').length === 0 || $('#warnings').find('button.focus').prev('button').length === 0 ) {
  2872. $('#warnings').find('button').last('button').focus().addClass('focus').siblings().removeClass('focus').blur();
  2873. } else {
  2874. $('#warnings').find('button.focus').prev('button').focus().addClass('focus').siblings().removeClass('focus').blur();
  2875. }
  2876. }
  2877. }
  2878.  
  2879. $(':focus').blur(); // Need this to able to select grid items after clicking close button (or other buttons).
  2880.  
  2881. switch ( e.key ) {
  2882. case 'ArrowUp':
  2883. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) { // Cmd/Ctrl + up arrow = go to parent directory
  2884. if ( $('#parents_dir_menu + ul a').length < 1 ) {
  2885. return;
  2886. } else {
  2887. showWarning( 'clickThis', $('#parent_dir_menu').attr('id') );
  2888. }
  2889. break;
  2890. }
  2891. if ( $('*[contentEditable="true"]').is(':focus') ) { // Allow arrow navigation within content_editable elements
  2892. return;
  2893. }
  2894. e.preventDefault();
  2895. showWarning( 'indexNavigation', 'ArrowUp' );
  2896. break;
  2897. case 'ArrowDown':
  2898. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey && $selected.hasClass('app') && $settings.apps_as_dirs === false ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable or open dir
  2899. return;
  2900. } else if ( $('*[contentEditable="true"]' ).is(':focus') ) {
  2901. return;
  2902. } else if ( (e.ctrl || e.metaKey) && $selected.hasClass('dir') ) {
  2903. $selected.find('a').trigger('dblclick');
  2904. break;
  2905. }
  2906. e.preventDefault();
  2907. showWarning( 'indexNavigation', 'ArrowDown' );
  2908. break;
  2909. case 'ArrowLeft':
  2910. if ( (e.ctrl || e.metaKey) || (e.ctrl && e.metaKey) ) {
  2911. return;
  2912. } else if ( $('*[contentEditable="true"]').is(':focus') ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable
  2913. return;
  2914. }
  2915. if ( (e.altKey && e.shiftKey) || e.altKey && !e.metaKey && !e.ctrlKey ) { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  2916. mediaSkip(e);
  2917. return;
  2918. } else {
  2919. showWarning( 'indexNavigation','ArrowLeft' );
  2920. }
  2921. break;
  2922. case 'ArrowRight':
  2923. if ( e.metaKey && !e.altKey && !e.shiftKey && $selected.hasClass('dir') ) { // Open dir with Cmd/Ctrl + Right Arrow
  2924. $selected.find('a').trigger('dblclick');
  2925. return;
  2926. }
  2927. if ( (e.ctrl || e.metaKey) || (e.ctrl && e.metaKey) ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable
  2928. return;
  2929. } else if ( $('*[contentEditable="true"]').is(':focus') || $selected.hasClass('dir ignore') ) {
  2930. return;
  2931. }
  2932. if ( (e.altKey && e.shiftKey) || e.altKey && !e.metaKey && !e.ctrlKey ) { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  2933. mediaSkip(e);
  2934. return;
  2935. } else {
  2936. showWarning( 'indexNavigation','ArrowRight' );
  2937. }
  2938. break;
  2939. case ' ': // space
  2940. if ( $content_pane.hasClass('has_audio') || $content_pane.hasClass('has_video') ) { // Play/pause media (space bar)
  2941. e.preventDefault();
  2942. playPauseMedia();
  2943. } else {
  2944. alphaNav(e);
  2945. }
  2946. break;
  2947. case 'Enter': // Open directories (or ignore)
  2948. if ( $selected.hasClass('app') && $settings.apps_as_dirs === false ) {
  2949. break;
  2950. } else {
  2951. if ( $selected.hasClass('dir') ) {
  2952. $selected.find('a').trigger('dblclick');
  2953. } else if ( $selected.hasClass('playing') || $selected.hasClass('video') ) { // content_audio or content_video
  2954. playPauseMedia();
  2955. } else {
  2956. $selected.click();
  2957. }
  2958. }
  2959. break;
  2960. // Alphabetical navigation
  2961. 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':
  2962. case 'a': case 'b': case 'c': case 'f': case 'h': case 'j': case 'k': case 'l': case 'm': case 'n': case 'p': case 'q': case 's': case 't': case 'u': case 'v': case 'x': case 'y': case 'z':
  2963. 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 'π':
  2964. 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 '≠':
  2965. case '⁄': case '€': case '‹': case '›': case 'fl': case '‡': case '°': case '·': case '‚': case '±': case '¯': case '˘': case '¿':
  2966. case 'ı': case '': case '´': case '˝': case 'ˆ': case '': case '˜': case '‰': case 'ˇ': case '¨': case '◊': case '„': case '˛': case '¸':
  2967. // 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 'ϖ':
  2968. // 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 'ϒ':
  2969. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2970. alphaNav(e);
  2971. }
  2972. break;
  2973. case 'd': // Cmd/Ctrl + D: Toggle Details
  2974. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2975. e.preventDefault();
  2976. $show_details.click();
  2977. } else {
  2978. alphaNav(e);
  2979. }
  2980. break;
  2981. case 'e': // Cmd/Ctrl + E: Show Text Editor
  2982. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2983. e.preventDefault();
  2984. $('#text_editor_row').find('a').click();
  2985. } else {
  2986. alphaNav(e);
  2987. }
  2988. break;
  2989. case 'g': // Cmd/Ctrl + G: Show image Grid
  2990. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2991. e.preventDefault();
  2992. $grid_btn.click();
  2993. } else {
  2994. alphaNav(e);
  2995. }
  2996. break;
  2997. case 'i': // Cmd/Ctrl + I: Toggle Invisibles
  2998. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2999. e.preventDefault();
  3000. $inv_checkbox.find('input').click();
  3001. } else {
  3002. alphaNav(e);
  3003. }
  3004. break;
  3005. case 'o': // Cmd/Ctrl + Shift + O: Open selected item in new window
  3006. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && e.shiftKey && !e.altKey ) {
  3007. window.open( thisLink($selected_file()) );
  3008. } else {
  3009. alphaNav(e);
  3010. }
  3011. break;
  3012. case 'r': // Cmd/Ctrl + Shift + R: Refresh
  3013. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  3014. e.preventDefault();
  3015. $('#reload_btn').click();
  3016. } else {
  3017. alphaNav(e);
  3018. }
  3019. break;
  3020. case 'w': // Close content pane if Close button visible with Cmd/Crtl + W
  3021. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  3022. if ( $content_pane.attr('class').indexOf('has_') > -1 || $('#toggle_info').hasClass('selected') ) {
  3023. e.preventDefault();
  3024. $('#close_btn').click();
  3025. }
  3026. } else {
  3027. alphaNav(e);
  3028. }
  3029. break;
  3030. case '\\':
  3031. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  3032. e.preventDefault();
  3033. $('#toggle_split_view').click();
  3034. } else {
  3035. alphaNav(e);
  3036. }
  3037. break;
  3038. case '=':
  3039. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  3040. e.preventDefault();
  3041. $('#increase').click();
  3042. } else {
  3043. alphaNav(e);
  3044. }
  3045. break;
  3046. case '-':
  3047. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  3048. e.preventDefault();
  3049. $('#decrease').click();
  3050. } else {
  3051. alphaNav(e);
  3052. }
  3053. break;
  3054. case 'tab':
  3055. break;
  3056. case 'Escape':
  3057. $('*:focus').blur();
  3058. break;
  3059. } // end switch
  3060. });
  3061. // ***** END KEYBOARD EVENTS ***** //
  3062.  
  3063. // ***** GRID SETUP ***** //
  3064. // Create Font Grid Items
  3065. function fontGridItems() {
  3066. let $font_grid_items_arr = [];
  3067. let $font_files = $dir_list_body.find('.font');
  3068. let fontGridStyles = $font_grid_styles.sheet;
  3069. for ( let i = 1; i < $font_files.length; i++ ) {
  3070. let newGridItem = showFont($font_files[i],true,fontGridStyles);
  3071. $font_grid_items_arr.push( newGridItem );
  3072. }
  3073. return $font_grid_items_arr;
  3074. }
  3075. // Create Image Grid Items
  3076. function imageGridItems() {
  3077. let $image_grid_items_arr = [];
  3078. let $image_files = $dir_list_body.find('.image');
  3079. for ( let i = 0; i < $image_files.length; i++ ) {
  3080. const $this_link = thisLink($image_files[i]);
  3081. const exts = $row_types.image.filter( ext => $.inArray(ext, $row_settings.ignore) == -1 ); // decide which image files can be displayed
  3082. const $title_name = $this_link.slice($this_link.lastIndexOf('/') + 1);
  3083. if ( $.inArray( thisExt($image_files[i]), exts ) > -1 ) { // if this row file ext is in the image extension array
  3084. let item = '<div class="image_grid_item"><a href="'+$this_link+'"><img src="'+$this_link+'" title="'+$title_name+'" loading="lazy" /></a></div>';
  3085. $image_grid_items_arr.push( item );
  3086. }
  3087. }
  3088. return $image_grid_items_arr;
  3089. }
  3090. // Make Grids
  3091. function makeGrids(id) {
  3092. closeGrid();
  3093. $content_pane.removeClass('has_hidden_grid').addClass('has_grid');
  3094. if ( id === 'show_font_grid' || !$body.hasClass('has_images') ) { // only show font grid
  3095. $title.removeClass().addClass('font_grid');
  3096. $content_grid.addClass('has_font_grid');
  3097. $content_grid.append( fontGridItems() );
  3098. } else if ( id === 'show_image_grid' || !$body.hasClass('has_fonts') ) { // only show image grid
  3099. $title.removeClass().addClass('image_grid');
  3100. $content_grid.addClass('has_image_grid');
  3101. $content_grid.append( imageGridItems() );
  3102. } else { // show grid of both images and fonts
  3103. $title.removeClass();
  3104. if ( $body.hasClass() ) { $content_grid.addClass('has_image_grid'); } else { $content_grid.addClass('has_grid'); }
  3105. $content_grid.append( imageGridItems(), fontGridItems() );
  3106. }
  3107. }
  3108. // Click grid button
  3109. $sidebar_header.on('click', '#grid_btn, #show_font_grid, #show_image_grid', function(e) {
  3110. e.stopPropagation();
  3111. makeGrids($(this).attr('id'));
  3112. showGrid();
  3113. });
  3114.  
  3115. // ***** SCALE PREVIEWED IMAGES & FONTS ***** //
  3116. // Scale Fonts
  3117. function scaleFonts(incr, y) {
  3118. const $em = parseInt(getComputedStyle(document.body).fontSize); // pts/em
  3119. const getFontSize = function(el) { return parseFloat(el.css('font-size')); };
  3120. if ( y === 'decrease' ) { incr = 1/incr; }
  3121. if ( $content_pane.hasClass('has_grid') && ( $content_grid.hasClass('has_font_grid') || $content_grid.hasClass('has_grid') ) ) {
  3122. $content_grid.css({'font-size':( getFontSize($content_grid)/$em * incr ) +'em'});
  3123. return;
  3124. }
  3125. if ( $content_pane.hasClass('has_font') ) {
  3126. $content_font.css({'font-size':( getFontSize($content_font)/$em * incr ) +'em'});
  3127. return;
  3128. }
  3129. }
  3130. // Scale Images
  3131. function scaleImages(incr, y) {
  3132. if (y === 'decrease' ) { incr = 1/incr; }
  3133. if ( $content_pane.hasClass('has_image') || $content_pane.hasClass('has_zoom_image') && !$content_pane.hasClass('has_grid') ) {
  3134. var $image_width = Math.round( $content_image.width() ) * incr; // increment image size
  3135. var $image_height = Math.round( $content_image.height() ) * incr; // increment image size
  3136. $content_pane.removeClass('has_zoom_image').addClass('has_image');
  3137. $content_image.css({'width':$image_width +'px', 'height': $image_height +'px', 'max-width':'none', 'max-height':'none' });
  3138. $content_container.scrollLeft( ( $image_width - $(window).width() )/2 ) ;
  3139. // if image is wider or taller than the window height, adjust position to enable scrolling:
  3140. if ( ( ( $image_width - $(window).width() )/2 ) > 0 ) { $content_image.css({'left':0}); } else { $content_image.css({'left':'unset'}); }
  3141. if ( ( ( $image_height - $(window).height() )/2 ) > 0 ) { $content_image.css({'top':0}); } else { $content_image.css({'top':'unset'}); }
  3142. return;
  3143. }
  3144. if ( $content_pane.hasClass('has_grid') && ( $content_grid.hasClass('has_image_grid') || $content_grid.hasClass('has_grid') ) ) {
  3145. let $image_grid_item_width = Number.parseFloat( $('.image_grid_item img').width(),10) * incr;
  3146. let $image_grid_item_height = Number.parseFloat( $('.image_grid_item img').height(),10) * incr;
  3147. let $image_grid_item_maxwidth = Number.parseFloat( $('.image_grid_item img').css('maxWidth'),10) * incr;
  3148. let $image_grid_item_maxheight = Number.parseFloat( $('.image_grid_item img').css('maxHeight'),10) * incr;
  3149. // prevent reducing grid image size on first scale click:
  3150. if ( $image_grid_item_width < $image_grid_item_maxwidth ) { $image_grid_item_width = $image_grid_item_maxwidth; }
  3151. if ( $image_grid_item_height < $image_grid_item_maxheight ) { $image_grid_item_height = $image_grid_item_maxheight; }
  3152. // set grid properties
  3153. $content_grid.css({'grid-template-columns':'repeat(auto-fill, minmax('+ ($image_grid_item_width +16) +'px, auto ) )'});
  3154. $content_grid.find('img').css({'max-width':( $image_grid_item_width ) +'px', 'max-height':( $image_grid_item_height ) +'px'});
  3155. return;
  3156. }
  3157. }
  3158. // Scale Fonts and Images
  3159. function scalePreviewItems(y) { // combine scaling into one function
  3160. scaleImages( 1.125, y );
  3161. scaleFonts( 1.125, y );
  3162. }
  3163. // Scale Content
  3164. $('#scale').on('click','span',function(e) {
  3165. e.preventDefault();
  3166. e.stopPropagation();
  3167. let val = ( $(this).attr('id') === 'increase' ? 'increase' : 'decrease' );
  3168. scalePreviewItems(val);
  3169. });
  3170. // Zoom Images on click
  3171. function zoomImage(e) {
  3172. const $this_link = $content_image.attr('src');
  3173. const $offset = $content_image.offset();
  3174. const $this_width = $content_image.width();
  3175. const $this_height = $content_image.height();
  3176. const percentX = ( e.pageX - $offset.left ) / $this_width;
  3177. const percentY = ( e.pageY - $offset.top ) / $this_height;
  3178.  
  3179. if ( ( $content_image.attr('style') !== '' || $content_image.attr('style') !== undefined ) ) { $content_image.attr('style',''); }
  3180. if ( $this_link !== undefined ) {
  3181. getDimensions( $this_link, function( width, height ) {
  3182. if ( width < $content_container.width() && height < $content_container.height() ) { // don't zoom small images
  3183. $content_pane.removeClass('has_zoom_image').addClass('has_image'); // remove zoom classes in case window resized after zoom
  3184. } else if ( width > $content_container.width() && height < $content_container.height() ) { // scroll-x to click position, center vertically
  3185. $content_pane.toggleClass('has_image has_zoom_image');
  3186. if ( $content_pane.hasClass('has_image') ) { $content_image.css({'margin-top':0}); } else { $content_image.css({'margin-top': ($content_container.height() - $content_image.height() )/2 }); }
  3187. $content_container.scrollLeft( width * percentX - ( $content_container.width() * percentX ) ) ;
  3188. } else if ( width < $content_container.width() && height > $content_container.height() ) { // center horizonally
  3189. $content_pane.toggleClass('has_image has_zoom_image');
  3190. if ( $content_pane.hasClass('has_image') ) { $content_image.css({'margin-left':0}); } else { $content_image.css({'margin-left': ($content_container.width() - $content_image.width() )/2 }); }
  3191. $content_container.scrollTop( height * percentY - ( $content_container.height() * percentY ) );
  3192. } else {
  3193. $content_pane.toggleClass('has_image has_zoom_image');
  3194. $content_container.scrollLeft( width * percentX - ( $content_container.width() * percentX ) ) ;
  3195. $content_container.scrollTop( height * percentY - ( $content_container.height() * percentY ) );
  3196. }
  3197. });
  3198. }
  3199. }
  3200. $content_image.on('click', function(e) {
  3201. zoomImage(e);
  3202. });
  3203. // ***** END SCALE PREVIEW ITEMS ***** //
  3204.  
  3205.  
  3206. // ***** AUDIO CONTENT ***** //
  3207. // Update Playlist
  3208. function updatePlaylist() {
  3209. let playlist = [];
  3210. $audio_files.not('.unchecked').each(function() {
  3211. playlist.push( thisID( $(this) ) );
  3212. });
  3213. return playlist;
  3214. }
  3215. // Randomize Shuffle List
  3216. function shuffleArray(array) {
  3217. for ( let i = array.length - 1; i > 0; i-- ) {
  3218. const j = Math.floor(Math.random() * (i + 1));
  3219. [array[i], array[j]] = [array[j], array[i]];
  3220. }
  3221. return array;
  3222. }
  3223. // Attach Shuffle List data to $audio_player
  3224. function updateShuffleList(id) {
  3225. if ( !$body.hasClass('shuffle_audio') ) {
  3226. return;
  3227. } else if ( id !== undefined ) { // don't include .playing and .unchecked track in shufflelist
  3228. let shuffleList = $audio_player.data('shufflelist');
  3229. if ( $(document.getElementById(id)).hasClass('unchecked') || $(document.getElementById(id)).hasClass('playing') ) {
  3230. shuffleList.splice(shuffleList.indexOf(id), 1);
  3231. $audio_player.data('shufflelist',shuffleList);
  3232. } else {
  3233. shuffleList.push(id);
  3234. shuffleList = shuffleArray( shuffleList );
  3235. }
  3236. } else {
  3237. let shuffleList = shuffleArray( updatePlaylist() );
  3238. $audio_player.data('shufflelist',shuffleList);
  3239. }
  3240. }
  3241.  
  3242. // Check/Uncheck Audio/Video Files
  3243. function toggleChecked(e) {
  3244. e.stopPropagation();
  3245. $(this).blur();
  3246. thisRow(this).toggleClass('unchecked');
  3247. updateShuffleList(thisRow(this).attr('id'));
  3248. }
  3249. $media_files.on('click','input', toggleChecked );
  3250. // Check/Uncheck all Audio/Video Files
  3251. function toggleAllChecked(e) {
  3252. e.stopPropagation();
  3253. $dir_list_row.find('input').trigger('click');
  3254. updateShuffleList();
  3255. }
  3256. $dir_list.find('#play_toggle').on('click', toggleAllChecked );
  3257.  
  3258. // Is Playing
  3259. function isPlaying(el) {
  3260. return (el !== undefined && el.get(0).currentTime > 0 && !el.get(0).paused && !el.get(0).ended); // returns true if all conditions are true
  3261. }
  3262. // Play Media
  3263. function playMedia(task) {
  3264. if ( $playing_file().hasClass('audio') ) { $audio_player.trigger(task); } else { $content_video.trigger(task); }
  3265. }
  3266. // Skip media tracks +/-10/30 seconds
  3267. function mediaSkip(e) {
  3268. const factor = ( e.key === 'ArrowLeft' ? -1 : 1 ); // forward or backward?
  3269. const skip = ( e.altKey && e.shiftKey ? 30 : e.altKey ? 10 : null ); // 30s or 10s?
  3270. const $player = ( $playing_file().hasClass('audio') ? $audio_player : $content_video ); // audio or video?
  3271. const time = $player.prop('currentTime'); // current time
  3272. $player.prop('currentTime', time + factor*(skip)); // set time
  3273. }
  3274.  
  3275. // Play/Pause Audio/Video
  3276. function playPauseMedia() {
  3277. let $player = ( $content_pane.hasClass('has_audio') ? $('#audio') : $('#content_video') );
  3278. if ( isPlaying( $player ) ) { $player.trigger('pause'); } else { $player.trigger('play'); }
  3279. }
  3280. // Play Next Track
  3281. function playPrevNextTrackBtn(el) {
  3282. let key = ( el.attr('id') === 'prev_track' ? 'ArrowLeft' : 'ArrowRight' );
  3283. playPrevNextTrack(key);
  3284. }
  3285. // Prev/Next Track buttons
  3286. $('.prev_next_track_btn').on( 'click', function() { playPrevNextTrackBtn( $(this) ); });
  3287.  
  3288. // Toggle Shuffle Play
  3289. $('#checkbox_div').on('click','#shuffle', function() {
  3290. $body.toggleClass('shuffle_audio');
  3291. updateShuffleList();
  3292. if ( $body.hasClass('shuffle_audio') && $('.playing').length === 0 ) {
  3293. playPrevNextTrack('ArrowRight');
  3294. } else {
  3295. // do nothing: i.e., allow current track to continue playing
  3296. }
  3297. });
  3298. // Toggle Loop Play
  3299. $('#checkbox_div').on('click','#loop', function() {
  3300. $body.toggleClass('loop_audio');
  3301. document.getElementById('audio').toggleAttribute('loop');
  3302. });
  3303.  
  3304. // Initialize Audio
  3305. function initMedia() {
  3306. $('#audio, #content_video').on('ended', function() {
  3307. playPrevNextTrack('ArrowRight');
  3308. scrollThis('tbody','playing');
  3309. });
  3310. }
  3311. // ***** END AUDIO PLAYBACK ***** //
  3312.  
  3313. // ***** IFRAME SETUP ***** //
  3314. // For directory display or editable text files
  3315. // If row is a directory, set up iFrameDirUI(); if it's an editable text document, set up iFrameTextEditingUI().
  3316. function setUpIframeUI(bool) {
  3317. if ( window.self !== window.top ) {
  3318. const $textFiles = $row_types.markdown.concat($row_types.text, $row_types.code); // define which files are editable
  3319. // if selected index item is a directory, set up the directory UI....
  3320. if ( window.location.pathname.endsWith('/') ) {
  3321. iFrameDirUI( $('#iframe_body') );
  3322. }
  3323. if ( JSON.parse(bool) === true && $textFiles.includes( window.location.pathname.slice( window.location.pathname.lastIndexOf('.') + 1 ) ) ) {
  3324. $iFrame_head.append('<style>'+ $text_editing_style_rules +'</style>');
  3325. $iFrame_head.append('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>');
  3326. let splitClass = ( getQuery('split_view') === 'true' ? 'split_view' : '' );
  3327. let viewClass = getQuery('default_text_view');
  3328. TextEditing( '#iframe_body' );
  3329. $iframe_body.removeClass().addClass(splitClass).addClass(viewClass).prepend($warnings);
  3330. $('#content_source').removeClass().prop('disabled',false);
  3331. }
  3332. }
  3333. }
  3334. // IFRAME DIRECTORY Prep
  3335. function iFrameDirUI(el) { // el = iframeBody
  3336. let parentLink = decodeURIComponentSafe(window.location.pathname);
  3337. parentLink = parentLink.split('/').slice(0,-2).join('/') + window.location.search;
  3338. let queryPrefs = window.location.search;
  3339. queryPrefs = queryPrefs.slice(1).split('&');
  3340. let sortPref = queryPrefs[0].slice(queryPrefs[0].indexOf('=') + 1); // sort determined by parent's current sorting pref
  3341. let numbersPref = queryPrefs[1];
  3342. let viewSourcePref = queryPrefs[2];
  3343.  
  3344. if ( viewSourcePref === 'view_source=true' ) {
  3345. return;
  3346. } else {
  3347. if ( numbersPref === 'show_numbers=true' ) { $('#iframe_body').addClass('show_numbers'); }
  3348.  
  3349. $iFrame_head.find('style').remove();
  3350. $iFrame_head.append('<style>'+ $iframe_styles +'</style>');
  3351.  
  3352. const parentLinkCell = '<tr id="parent"><th colspan=4><a href="'+ parentLink +'">Parent Directory</a></th></tr>';
  3353. const sortingRow = '<tr><th class="sorting" id="sort_by_name" colspan="2"><span>Name</span></th><th class="sorting" id="sort_by_default" colspan="2"><span>Default</span></th></tr><tr id="sorting_row"><th class="sorting" id="sort_by_ext"><span>Ext</span></th><th class="sorting" id="sort_by_size"><span>Size</span></th><th class="sorting" id="sort_by_date"><span>Date</span></th><th class="sorting" id="sort_by_kind"><span>Kind</span></th></tr>';
  3354. const preppedIndex = makeNewIndex($('#iframe_body'),sortPref);
  3355. const iFrameTable = $('<table id="dir_list"><thead id="thead">'+ parentLinkCell + sortingRow +'</thead><tbody id="tbody"></tbody></table>');
  3356. // append prepped index
  3357. el.empty().append(iFrameTable).find('tbody').append(preppedIndex);
  3358. $('#iframe_body').find('#tbody').css({'top':$('#iframe_body').find('#thead').height() +'px'});
  3359. $('#iframe_body').data('sort_direction',1).find('#dir_list').addClass('sort_by_'+ sortPref); // initial directory sort
  3360. }
  3361. }
  3362.  
  3363. // IFRAME Directory sorting
  3364. $('#iframe_body').on('click','.sorting', function() {
  3365. const $dir_list_row = $('#iframe_body').find('#tbody').find('tr');
  3366. const id = $(this).attr('id');
  3367. if ( $('#iframe_body').data('sorting') !== id ) { // if clicking sorting item for the first time
  3368. $('#iframe_body').data('sorting',id);
  3369. $('#iframe_body').data('sorting',id).data('sort_direction', 1 );
  3370. $(this).removeClass('down');
  3371. } else { // clicking the same sorting item again -- reverse sort order
  3372. $('#iframe_body').data('sort_direction', $('#iframe_body').data('sort_direction') * -1 );
  3373. $(this).toggleClass('down');
  3374. }
  3375. const sort_direction = $('#iframe_body').data('sort_direction');
  3376. const $sorted_index = sortDirList( $dir_list_row, id, sort_direction );
  3377. $('#iframe_body').find('#dir_list').removeClass().addClass(id).find('#tbody').empty().append($sorted_index);
  3378. });
  3379.  
  3380. //***** TEXT EDITING PANE *****//
  3381. function textEditorUI() { //
  3382. if ( $content_text.children().length === 0 ) { // only add UI once
  3383. $body.addClass('has_text');
  3384. $content_pane.removeClass('has_dir');
  3385. if ( $content_pane.hasClass('has_grid') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  3386. TextEditing('#content_text');
  3387. $content_text.find('#content_preview').html($content_text.find('#content_source').val()); // make sure any source text is also previewed.
  3388. if ( getQuery('split_view') === 'true' || getQuery('default_text_view') === 'source_text' ) { $content_text.find('#content_source').focus(); }
  3389. } else { // show text editor
  3390. if ( !$body.hasClass('has_text') ) {
  3391. $body.addClass('has_text');
  3392. } else if ( $body.hasClass('has_text') || $body.hasClass('has_hidden_text') ) {
  3393. $body.toggleClass('has_text has_hidden_text');
  3394. }
  3395. if ( $content_pane.hasClass('has_grid') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  3396. if ( $content_pane.hasClass('has_hidden_grid ') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  3397. }
  3398. setContentTitle();
  3399. setContentHeight();
  3400. }
  3401. // show text editor pane
  3402. $('#text_editor, #text_editor_row').on('click', function(e) {
  3403. e.preventDefault();
  3404. textEditorUI();
  3405. });
  3406.  
  3407. // Main Text Editing Function
  3408. function TextEditing(id) { // container_el = $content_text or $content_iframe body
  3409. let container_el = $(id);
  3410. const $srctxt = ( container_el.find('> pre').length ? container_el.find('> pre').text() : container_el.html() ); // source text equals file content or nothing
  3411.  
  3412. MDbuildUI(id);
  3413.  
  3414. const $toolbar = container_el.find('#toolbar');
  3415. const $source = container_el.find('#content_source');
  3416. const $preview = container_el.find('#content_preview');
  3417. const $MDhandle = container_el.find('#text_editing_handle');
  3418.  
  3419. MDsetupTextEditingUI(id,$srctxt);
  3420. // Toolbar button functions
  3421. $toolbar.on('click','li,span',function(e) {
  3422. e.stopPropagation();
  3423. MDtoolBarFunctions($(this).attr('id'));
  3424. });
  3425. $(window).on('resize',function() {
  3426. $source.add($preview).add($MDhandle).attr('style','');
  3427. });
  3428. $('body#top').on('input', '#content_source', function() {
  3429. $source.add($preview).css({'height':$('#main_content').height() - $('#content_header').height() - 32 });
  3430. });
  3431.  
  3432. // Resize
  3433. $MDhandle.on('mousedown', function(e) {
  3434. e.stopPropagation();
  3435. MDresizeSplit($MDhandle,$source,$preview);
  3436. });
  3437. // Click labels to toggle checkboxes
  3438. $preview.add($toolbar).on('click','label', function(e) {
  3439. e.stopPropagation();
  3440. $(this).siblings('input').click();
  3441. });
  3442. // Sync scroll
  3443. $source.on('scroll',function() { MDsyncScroll(this); });
  3444. $preview.on('scroll',function() { MDsyncScroll(this); });
  3445.  
  3446. // TEXT EDITING
  3447. // Generate Preview
  3448. const $source_text = ( $source.length === 0 ? '' : $source.val() );
  3449. MDmarkdown( $source_text, $preview );
  3450.  
  3451. // Live preview update, and set edited classes for unsaved warning
  3452. $source.on('input', function() {
  3453. if ( !$('body').hasClass('edited') && $(this).parents('#top').length === 1 ) {
  3454. $('body#top').addClass('edited');
  3455. }
  3456. if ( !$('body').hasClass('edited') && $(this).parents('#iframe_body').length === 1 ) {
  3457. $('body#iframeBody').addClass('edited');
  3458. sendMessage('top','iframe_edited','','');
  3459. }
  3460. MDlivePreview($source,$preview);
  3461. });
  3462. // Checklists
  3463. MDsetChecklistClass();
  3464. // Live checkboxes
  3465. $preview.on('click','.checklist input',function(e) {
  3466. e.stopPropagation();
  3467. MDliveCheckBoxes($(this),$source,$preview);
  3468. });
  3469. // Preview TOC click navigation
  3470. $preview.on('click','.table-of-contents a',function(e) {
  3471. e.preventDefault();
  3472. MDtocClick($(this),$preview);
  3473. });
  3474. $preview.on('click','.uplink',function(e) {
  3475. e.stopPropagation();
  3476. MDheaderClick($preview);
  3477. });
  3478. }
  3479. ///// END MAIN MD FUNCTION
  3480.  
  3481. // MARKDOWN Functions
  3482. // MD Build UI
  3483. function MDbuildUI(id) {
  3484. const toggleSplitBtn = $('<li id="toggle_split" title="Toggle Split"></li>');
  3485. const syncScrollEl = $('<li id="sync_scroll"><input name="sync_scroll" type="checkbox"><label for="sync_scroll">Sync Scroll</label></li>');
  3486. const toggleSrcBtn = $('<li id="show_source" title="Show Source"></li>');
  3487. const togglePreviewBtn = $('<li id="show_preview" title="Show Preview"></li>');
  3488. const clearTextBtn = $('<li id="clear_text" title="Clear Text">Clear</li>');
  3489. const saveBtn = $('<li id="save_btn" title=""><div><span id="save_text"><a target="_blank">Save Source</a></span><span id="save_HTML">Save HTML</span></div></li>');
  3490. const buttonsCont = $('<ul id="toolbar"></ul>');
  3491. buttonsCont.append(toggleSrcBtn, togglePreviewBtn, toggleSplitBtn, syncScrollEl, saveBtn, clearTextBtn);
  3492. const textEditingUI = '<textarea id="content_source"></textarea><div id="content_preview" class="markdown-body"></div><div id="text_editing_handle"></div>';
  3493. // append the UI to the container_el
  3494. $(id).prepend(buttonsCont).append(textEditingUI);
  3495. }
  3496.  
  3497. // MD Set up UI
  3498. function MDsetupTextEditingUI(id,sourceText) {
  3499. $(id).find('pre').first().remove();
  3500. $(id).find('#content_source').val(sourceText); // set source text from pre
  3501. if ( getQuery('split_view') === 'true' ) { $('body').addClass('split_view'); } else { $('body').removeClass('split_view'); }
  3502. if ( getQuery('default_text_view') === 'preview' ) { $('body').addClass('preview_text').removeClass('source_text'); } else { $('body').addClass('source_text').removeClass('preview_text'); }
  3503. if ( getQuery('sync_scroll') === 'true' ) { $('#sync_scroll input').prop({checked:true}); }
  3504. }
  3505. // MD UI Buttons functions
  3506. function MDtoolBarFunctions(id) {
  3507. let $thisFileName;
  3508. let container_el = $(getElById(id)).closest('body');
  3509. let sourceEl = container_el.find('#content_source');
  3510. let previewEl = container_el.find('#content_preview');
  3511. if ( $body.hasClass('has_text') ) {
  3512. $thisFileName = 'untitled';
  3513. } else {
  3514. $thisFileName = decodeURI(window.location.pathname.slice(window.location.pathname.lastIndexOf('/') + 1));
  3515. }
  3516. const $saveHTMLOpen = '<!DOCTYPE html><html><head><title></title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link><style></style><script></script></head><body lang="en" class="markdown-body">';
  3517. const $saveHTMLClose = '</body></html>';
  3518. switch (id) {
  3519. case 'toggle_split':
  3520. $('body').toggleClass('split_view').find('#content_source,#content_preview,#text_editing_handle').attr('style','');
  3521. if ( container_el.hasClass('source_text') ) {
  3522. sourceEl.focus();
  3523. document.getElementById('content_source').setSelectionRange(0,0);
  3524. }
  3525. break;
  3526. case 'show_source':
  3527. container_el.removeClass('split_view preview_text').addClass('source_text').find('#content_source,#content_preview,#text_editing_handle').attr('style',''); // remove styles in case split has been resized
  3528. sourceEl.css({'width':'100%'}).focus();
  3529. document.getElementById('content_source').setSelectionRange(0,0);
  3530. break;
  3531. case 'show_preview':
  3532. container_el.removeClass('split_view source_text').addClass('preview_text').find('#content_source,#content_preview,#text_editing_handle').attr('style','');
  3533. break;
  3534. case 'clear_text':
  3535. container_el.addClass('has_warning').find('#warnings').removeClass().addClass('clear');
  3536. break;
  3537. case 'save_text':
  3538. saveMD( $thisFileName, sourceEl.val() );
  3539. break;
  3540. case 'save_HTML':
  3541. saveMD( $thisFileName.slice(0,$thisFileName.lastIndexOf('.') + 1) + 'html', $saveHTMLOpen + MDprepHTML(previewEl.html()) + $saveHTMLClose );
  3542. break;
  3543. }
  3544. }
  3545. // MD Custom pre- and post-processing for text.
  3546. function MDaddHeaderIDs(match, p1, p2, p3, offset, string) { // create header ids for TOC
  3547. return '<h'+ p1 +' id="'+ p3.toLowerCase().replace(/\s/g,'-') +'" ' + p2 +'>'+ p3;
  3548. }
  3549. function MDcustomPreProcess(src) {
  3550. return src; // we're not doing anything here just yet...
  3551. }
  3552. function MDcustomPostProcess(html) {
  3553. html = html.replace(/<(p|li|dt|dd)>\-*\s*\[\s*x\s*\]\s*(.+?)<\/(p|li|dt|dd)>$/gm,'<$1 class="checklist"><input type="checkbox" checked><label>$2</label></$3>') // checkboxes in p,li,dt,dd
  3554. .replace(/<(p|li|dt|dd)>-*\s*\[\s{1,}\]\s*(.+?)<\/(p|li|dt|dd)>$/gm,'<$1 class="checklist"><input type="checkbox"><label>$2</label></$3>') // checkboxes
  3555. // .replace(/<li><p class="checklist">"/g,'<li class="checklist"><p>')
  3556. .replace(/^<h(\d)([^>]*)>([^<]+)/gm, MDaddHeaderIDs) // add header IDs;
  3557. .replace(/<\/h(\d)>/g,'<span class="uplink">&uarr;</span></h$1>');
  3558. return html;
  3559. }
  3560. //MD Render markdown from preprocessed source text
  3561. function MDmarkdown(sourceText,previewEl) {
  3562. const MDit = window.markdownit({linkify:false,typography:false,html:true})
  3563. .use(window.markdownitMultimdTable, {enableMultilineRows: true})
  3564. .use(window.markdownitSub)
  3565. .use(window.markdownitSup)
  3566. .use(window.markdownitFootnote)
  3567. .use(window.markdownitCentertext)
  3568. .use(window.markdownitDeflist)
  3569. .use(window.markdownitTocDoneRight)
  3570. ;
  3571. let MDpreview = MDit.render( MDcustomPreProcess( sourceText ) );
  3572. previewEl.html( MDcustomPostProcess( MDpreview ) ); // set previewed html
  3573. }
  3574. // MD Live preview, add edited warning
  3575. function MDlivePreview(sourceEl,previewEl) {
  3576. MDmarkdown( sourceEl.val(),previewEl );
  3577. MDsetChecklistClass();
  3578. }
  3579.  
  3580. // MD Live Checkboxes prep: find each instance of [ ] or [x] and replace text in index = to clicked checkbox in Preview.
  3581. function MDreplaceAt(str, replacement, position) {
  3582. str = str.substring(0, position) + replacement + str.substring(position + replacement.length);
  3583. return str;
  3584. }
  3585. function MDreplaceNthSubStr(str,substr,replacement,index) {
  3586. let count = 0;
  3587. let found = substr.exec(str);
  3588. while ( found !== null ) {
  3589. if ( count === index ) {
  3590. return MDreplaceAt(str, replacement, found.index );
  3591. } else {
  3592. count++;
  3593. found = substr.exec(str);
  3594. }
  3595. }
  3596. }
  3597. // MD Live Checkboxes
  3598. function MDliveCheckBoxes(checkbox,sourceEl,previewEl) {
  3599. $('.checklist').removeClass('clicked');
  3600. checkbox.closest('p,li,dt,dd').addClass('clicked');
  3601. const thisIndex = previewEl.find('.checklist').index( $('.clicked') );
  3602. const srctext = sourceEl.val();
  3603. const substr = new RegExp(/\[\s*.\s*\]/g);
  3604. const replacement = ( checkbox.is(':checked') ? '[x]' : '[ ]' );
  3605. sourceEl.val( MDreplaceNthSubStr(srctext, substr, replacement, thisIndex) );
  3606. }
  3607. // MD Checkbox list class: Prevent checkbox lists from having list bullets
  3608. function MDsetChecklistClass() {
  3609. $('input[type="checkbox"]').closest('ul').addClass('no_list');
  3610. }
  3611.  
  3612. // MD Resize Split View
  3613. function MDresizeSplit(handle,sourceEl,previewEl) {
  3614. let $sidebarWidth = $('#sidebar').outerWidth();
  3615. let $pageWidth = window.innerWidth;
  3616.  
  3617. $(document).on('mousemove',function(e) {
  3618. e.stopPropagation();
  3619. e.preventDefault();
  3620. let pageX = e.pageX;
  3621. if ( pageX > $sidebarWidth + 100 && pageX < $pageWidth - 100 ) { // min widths
  3622. handle.css({'left': pageX - $sidebarWidth - 4 + 'px'});
  3623. sourceEl.css({'width': pageX - $sidebarWidth + 'px'});
  3624. previewEl.css({'left': sourceEl.outerWidth() + 'px'});
  3625. }
  3626. });
  3627. handle.on('mouseup',function() {
  3628. $(document).off('mousemove');
  3629. });
  3630. }
  3631. // MD UI Sync Scroll
  3632. function MDpercentage(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)); }
  3633. function MDsyncScroll(el1) {
  3634. let el2 = ( el1.getAttribute('id') === 'content_preview' ? document.getElementById('content_source') : document.getElementById('content_preview') );
  3635. if ( document.querySelector('input[name="sync_scroll"').checked ) {
  3636. el2.scrollTo( 0, (MDpercentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
  3637. }
  3638. }
  3639. // click TOC anchors
  3640. function MDtocClick(el,previewEl) {
  3641. let thisId = el.attr('href');
  3642. if ( thisId ) {
  3643. previewEl.scrollTop( $(thisId).offset().top - 48 );
  3644. }
  3645. }
  3646. // click Headers to return to TOC or top
  3647. function MDheaderClick(previewEl) {
  3648. if ( previewEl.find('.table-of-contents').length > 0 ) {
  3649. document.getElementsByClassName('table-of-contents')[0].scrollIntoView(true);
  3650. } else {
  3651. document.getElementById('preview').scroll(0,0);
  3652. }
  3653. }
  3654. // MD Clear text source
  3655. function clearText(container_el) {
  3656. if ( window.top !== window.self ) { // if iframe, send message to top (to remove iframe_edited class)
  3657. sendMessage('top','clear');
  3658. }
  3659. container_el.find('#content_source').show().focus().val('');
  3660. container_el.find('#content_preview').empty();
  3661. container_el.removeClass('edited has_warning');
  3662. }
  3663. // MD SAVE SOURCE or HTML
  3664. function MDprepHTML(data) {
  3665. data = data.replace(/<span\sclass="uplink">.<\/span>/g,'');
  3666. return data;
  3667. }
  3668. function saveMD(filename, data) {
  3669. let blob = new Blob([data], {type: 'text/plain'});
  3670. let downloadEl = window.document.createElement('a');
  3671. downloadEl.href = window.URL.createObjectURL(blob);
  3672. downloadEl.download = filename;
  3673. document.body.appendChild(downloadEl);
  3674. downloadEl.click();
  3675. document.body.removeChild(downloadEl);
  3676. URL.revokeObjectURL(blob);
  3677. if ( window.top !== window.self ) { // if iframe, send message to top
  3678. sendMessage('top','clear');
  3679. }
  3680. $('body,#content_source,#content_text').removeClass('edited');
  3681. }
  3682. // list of functions to remember while sending messages and then execute after warning button click
  3683. function doFunction(funcName,args) {
  3684. var funcDictionary = { 'setLocation':setLocation, 'resetContent':resetContent, 'closeContent':closeContent, 'clickThis':clickThis, 'clickRow':clickRow, 'doubleClickRow':doubleClickRow, 'indexNavigation':indexNavigation, 'clearText':clearText, 'null':null };
  3685. return funcName === 'null' ? null : funcDictionary[funcName](args);
  3686. }
  3687. // Show warning after certain user actions if text editor or iframe has edited text; otherwise do the action.
  3688. function showWarning(funcName,args) {
  3689. // Don't show the warning if func = indexNavigation or clickRow; i.e., just hide text editor;
  3690. // In other words, only show warning when changing directories or if iframe content has been edited
  3691. if ( ( $('body').hasClass('edited') && funcName !== 'indexNavigation' && funcName !== 'clickRow' ) || $('body').hasClass('iframe_edited') ) {
  3692. if ( $('body').hasClass('edited') ) { // show warning and text editor (if hidden)
  3693. $body.addClass('has_warning').find('#warnings').removeClass().addClass('unloading');
  3694. $body.removeClass('has_hidden_text').addClass('has_text');
  3695. }
  3696. if ( $('body').hasClass('iframe_edited') ) { // if iframe is edited, send unloading message
  3697. sendMessage('iframe','unloading',funcName,args); // upon receipt of message, iframe will show its warning message, based on the funcName
  3698. }
  3699. } else {
  3700. doFunction(funcName,args);
  3701. }
  3702. }
  3703. // Send a message to iframe or parent
  3704. function sendMessage(target,message,funcName,args) {
  3705. var messageObj = { 'messageContent': message, 'functionName': funcName, 'arguments': args };
  3706. if ( target === 'iframe' ) {
  3707. let contentIFrame = document.getElementById('content_iframe');
  3708. contentIFrame.contentWindow.postMessage( messageObj, '*' );
  3709. }
  3710. if ( target === 'top' ) {
  3711. window.parent.postMessage( messageObj, '*');
  3712. }
  3713. }
  3714. // Receive a message from iframe or parent, do appropriate action
  3715. function receiveMessage(e) {
  3716. if ( e.origin === 'null' || e.origin === $origin ) {
  3717. let $message = e.data.messageContent;
  3718. let funcName = e.data.functionName;
  3719. let args = e.data.arguments;
  3720.  
  3721. if ( $message === 'split_view' ) {
  3722. $iframe_body.toggleClass('split_view');
  3723. }
  3724. if ( $message === 'default_text_view' ) {
  3725. $iframe_body.toggleClass('preview_text source_text').removeClass('split_view');
  3726. }
  3727. // warn iframe that user wants to change iframes
  3728. if ( $message === 'unloading' && !$iframe_body.hasClass('has_warning') ) {
  3729. $iframe_body.addClass('has_warning').find('#warnings').removeClass().addClass('unloading').attr('data-function_name',funcName).attr('data-args',args);
  3730. }
  3731. // let top know iframe text has been edited
  3732. if ( $message === 'iframe_edited' && !$('body#top').hasClass('iframe_edited') ) {
  3733. $('body#top').addClass('iframe_edited');
  3734. }
  3735. if ( $message === 'ignore' || $message === 'clear' ) {
  3736. $('body#top').removeClass('iframe_edited');
  3737. if ( $message === 'ignore' ) { doFunction(funcName,args); }
  3738. }
  3739. }
  3740. }
  3741. window.addEventListener('message',receiveMessage,false);
  3742.  
  3743. // Edited Warning buttons: what to do when the user clicks a warning button
  3744. function editedWarningButtons(id) {
  3745. let btn = $(document.getElementById(id));
  3746. let container_el = btn.closest('body');
  3747. let func = $('#warnings').attr('data-function_name');
  3748. let args = $('#warnings').attr('data-args');
  3749. switch(id) {
  3750. case 'warning_ignore_btn': // do the user initiated func without saving the edited text
  3751. if ( window.self !== window.top ) { // if iframe, send message to top
  3752. sendMessage('top','ignore',func,args);
  3753. }
  3754. container_el.removeClass('edited has_text has_warning');
  3755. clearText(container_el);
  3756. break;
  3757. case 'warning_cancel_btn': // cancel the func
  3758. container_el.removeClass('has_warning').find('#warnings').removeClass();
  3759. break;
  3760. case 'warning_clear_btn': // clear the text editor
  3761. clearText(container_el);
  3762. break;
  3763. case 'warning_save_btn': // save the text
  3764. if ( window.top !== window.self ) { // if iframe, send message to top
  3765. sendMessage('top','clear');
  3766. }
  3767. container_el.removeClass('edited has_warning');
  3768. $('#save_text').click();
  3769. break;
  3770. case 'warning_ok_btn': // clear the text editor
  3771. $('body').removeClass('has_warning').find('#warnings').removeClass('local');
  3772. break;
  3773. }
  3774. }
  3775. $('#warnings').on('click','button',function(e) {
  3776. e.preventDefault();
  3777. editedWarningButtons( $(this).attr('id') );
  3778. });
  3779. // Edited Warning overlay: prevent user clicks on rest of UI
  3780. $('#overlay').on('click mousedown mouseup',function(e) {
  3781. e.preventDefault();
  3782. e.stopPropagation();
  3783. return;
  3784. });
  3785.  
  3786. // END Text Editing
  3787.  
  3788. })();
  3789. // THE END!