Supercharged Local Directory File Browser

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

目前为 2019-08-27 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Supercharged Local Directory File Browser
  3. // @version 4.1.1
  4. // @description Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds sidebar and preview pane; keyboard navigation and sorting; media playback with shuffle and loop; playlist support; edit, preview, and save markdown/plain text files; preview images and fonts; grid view; user-defined bookmarks; more.
  5. // @author gaspar_schot
  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. // @require https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js
  24.  
  25. // UPDATE URL
  26.  
  27. // NOTE: This script was developed in Vivaldi, running on Mac OS High Sierra. It has been tested in various Chrome and Gecko-based browsers.
  28. // It has been minimally tested on Windows and not at all on other OSes. It should work, but please report any issues.
  29. // The script does not work on local directories in Safari because Safari does not allow local directories to be browsed, but it will work on remote directories (or on local directories through a local server).
  30.  
  31. // NOTE: By default, Greasemonkey and Tampermonkey will not run scripts on file:/// urls, so for this script to work, you will have to enable it first.
  32. // For Tampermonkey, go to Chrome extension page, and tick the 'Allow access to file URLs' checkbox at the Tampermonkey extension section.
  33. // For Greasemonkey, open about:config and change greasemonkey.fileIsGreaseable to true.
  34.  
  35. // @namespace https://greasyfork.org/users/16170
  36. // ==/UserScript==
  37.  
  38. (function() {
  39. 'use strict';
  40. const $ = window.jQuery;
  41.  
  42. // ***** USER SETTINGS ***** //
  43.  
  44. const $settings = {
  45. // Paste your exported settings between the two lines below:
  46. //--------------------------------------------------------//
  47.  
  48. bookmarks: // N.B.: Directory links must end with "/", file links must end with another character.
  49. // You may add as many menus and links as you like; just copy the example below and edit as needed.
  50. // Local directory bookmarks must begin with "file:///"; external bookmarks must begin with the correct protocol ("http://" or "ftp://", etc.).
  51. // Note that because of same-origin security concerns, the browser will not allow you to navigate from an external webpage to a local directory.
  52. // But see this page for possible workarounds: https://stackoverflow.com/questions/39007243/cannot-open-local-file-chrome-not-allowed-to-load-local-resource
  53. [
  54. {
  55. "menu_title":"My Sample Menu",
  56. "links":
  57. [
  58. { "link_name":"My Directory Link 1", "link":"file:///Path/To/My/Directory/" },
  59. { "link_name":"My Directory Link 2", "link":"file:///Path/To/My/Directory_2/" },
  60. { "link_name":"My External Link", "link":"https://www.mywebpage.com/" },
  61. { "link_name":"My File Link", "link":"file:///Path/To/My/File.ext" },
  62. ]
  63. },
  64. {
  65. "menu_title":"My Second Sample Menu",
  66. "links":
  67. [
  68. { "link_name":"My Directory Link 1", "link":"file:///Path/To/My/Directory/" },
  69. { "link_name":"My Directory Link 2", "link":"file:///Path/To/My/Directory_2/" },
  70. { "link_name":"My External Link", "link":"https://www.mywebpage.com/" },
  71. { "link_name":"My File Link", "link":"file:///Path/To/My/File.ext" },
  72. ]
  73. },
  74. ],
  75. // GENERAL USER SETTINGS
  76. alternate_background: true, // If true (default true), alternate sidebar row background color.
  77. 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.
  78. // If false (default), treat apps as ignored files.
  79. 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).
  80. autoload_index_files: false, // If true (default: false), automatically select first "index.xxx" (.xxx !== .htm) file found in directory.
  81. // 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.
  82. theme: 'light', // Options: 'light' or 'dark'
  83. sort_by: 'default', // Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
  84. // default = Chrome sorting: dirs on top, files alphabetical.
  85. 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").
  86. // 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.)
  87. grid_font_size: 1, // Default = 1
  88. grid_image_size: 184, // Default = 184 (200px - 16px)
  89. show_details: true, // If true (default), hide file and directory details; if false, show them.
  90. show_ignored_files: false, // If true, ignored files will appear greyed-out.
  91. // If false (default), ignored files will be completely hidden from the file list;
  92. 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;
  93. // 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).
  94. show_invisibles: true, // Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
  95. show_numbers: true, // If true (default true), number index items
  96. UI_font: 'system-ui, sans-serif', // Choose an installed font for the UI; if undefined, use browser defaults instead.
  97. UI_font_size: '13px', // Choose a default UI font size; use any standard CSS units.
  98. use_custom_icons: true, // if true (default), use custom icons for dirs and files
  99. // if false, use browser/server default icons
  100. // TEXT EDITING SETTINGS
  101. enable_text_editing: true, // If true (default), allow plain text files to be edited.
  102. default_text_view: 'preview_text', // Options: 'source_text' or 'preview_text' for text editor.
  103. // Note that split_view = true overrides this setting.
  104. split_view: true, // If true, show split view on plain text file load.
  105. // if true (default), use default preview_text setting.
  106. sync_scroll: true // If true (default: true), show split view on plain text file load
  107. // if false, use default preview_text setting.
  108. //--------------------------------------------------------//
  109. // Paste your exported settings between the above two lines.
  110. };
  111.  
  112. // $ROW_TYPES:
  113. // DO NOT DELETE ANY EXISTING CATEGORIES!
  114. // Add file extensions for sorting and custom icon display to the existing categories.
  115. // You can also define your own new categories, but do not add an extension to more than one row_type category.
  116. // Do not add leading "." to the extensions.
  117. const $row_types = {
  118. // myRowType: ['ext1','ext2'],
  119. dir: ['/'],
  120. app: ['app/','app','bat','cgi','com','exe','jar','msi','wsf'],
  121. alias: ['alias','desktop','directory','lnk','symlink'],
  122. archive: ['7z','archive','b6z','bin','bzip','bz2','cbr','dmg','gz','iso','mpkg','pkg','rar','sit','sitx','tar','tar.gz','zip','zipx'],
  123. audio: ['aac','aif','aiff','ape','flac','m4a','mp3','ogg','opus','wav'],
  124. bin: ['a','dll','dylib','icc','msi','o'],
  125. 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'],
  126. database: ['accdb','db','dbf','mdb','pdb','sql', 'sqlite','sqlitedb','sqlite3'],
  127. ebook: ['azw','azw1','azw3','azw4','epub','ibook','kfx','mobi','tpz'],
  128. font: ['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'],
  129. graphics: ['afdesign','ai','book','dtp','eps','fm','icml','idml','indd','indt','inx','mif','pmd','pub','qxb','qxd','qxp','sla','swf'],
  130. htm: ['htm','html','xhtm','xhtml'],
  131. image: ['apng','bmp','gif','jpeg','jpg','png','svg','webp'],
  132. ignored_image: ['ai','arw','cr2','dng','eps','jpf','nef','psd','psd','raw','tif','tiff'],
  133. markdown: ['md','markdown','mdown','mkdn','mkd','mdwn','mdtxt','mdtext'],
  134. office: ['csv','doc','docx','epub','key','numbers','odf','ods','odt','pages','rtf','scriv','wpd','wps','xlr','xls','xlsx','xlm'],
  135. pdf: ['pdf'],
  136. system: ['DS_Store','ds_store','icon','ics'],
  137. text: ['log','nfo','txt','m3u'],
  138. video: ['m4v','mov','mp4','mpeg','webm']
  139. };
  140.  
  141. // $ROW_SETTINGS: Ignore or Exclude files by extension
  142. const $row_settings = {
  143. // 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).
  144. 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),
  145. // Exclude: Files with these exensions will not be inverted in dark mode
  146. exclude: ['htm','html','xhtm','xhtml']
  147. };
  148.  
  149. // ***** END USER SETTINGS ***** //
  150.  
  151. // ## FEATURES INCLUDE:
  152. // - Resizable sidebar and directory/file preview pane.
  153. // - Arrow navigation in sidebar:
  154. // - Up and Down Arrows select next/prev item.
  155. // - Left and Right Arrows select next/prev item of same type.
  156. // - Navigate sidebar by typed string.
  157. // - Show/Hide file details (size (if avail), date modified (if avail), kind, extension).
  158. // - Sort sidebar items by name or file details.
  159. // - Default sort = sort by name with folders on top.
  160. // - Preview all file types supported by browser (html, text, images, pdf, audio, video, etc.) and preview fonts.
  161. // - Preview and edit markdown and plain text files, with option to save files locally.
  162. // - Markdown rendered with markdownit.js ( https://github.com/markdown-it/markdown-it ).
  163. // - Uses Github Markdown styles for preview ( https://github.com/sindresorhus/github-markdown-css ), with a few customizations.
  164. // - Support for:
  165. // - TOC creation ( `${toc}` ) ( https://github.com/nagaozen/markdown-it-toc-done-right )
  166. // - Multimarkdown table syntax ( https://github.com/RedBug312/markdown-it-multimd-table )
  167. // - Live checkboxes ( `\[ ], [x]` ), allowed in lists and deflists.
  168. // - Superscript ( `^sup^` ) ( https://github.com/markdown-it/markdown-it-sup )
  169. // - Subscript ( `~sub~` ) ( https://github.com/markdown-it/markdown-it-sub )
  170. // - Definition lists ( https://github.com/markdown-it/markdown-it-deflist; for syntax, see http://pandoc.org/MANUAL.html#definition-lists )
  171. // - Centered text ( `->centered<-` ) ( https://github.com/jay-hodgson/markdown-it-center-text )
  172. // - Footnotes ( https://github.com/markdown-it/markdown-it-footnote )
  173. // - View source text, preview, or split pane with proportional sync scroll.
  174. // - Save edited source text or previewed HTML.
  175. // - Create and edit text in separate text editor.
  176. // - Audio and video playback, with shuffle, loop, skip audio +/- 10 or 30 sec via keyboard.
  177. // - Preview other files (e.g., lyrics or cover art) in same directory while playing audio.
  178. // - User setting to autoload cover art (if any images in directory, load "cover.ext" or first image found)
  179. // - Grid view for images and fonts.
  180. // - 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):
  181. // - Light or Dark theme.
  182. // - Bookmarks for local or remote directories.
  183. // - Default image grid size.
  184. // - Default UI font size and font-family.
  185. // - Default UI font and font-size.
  186. // - Default file sorting.
  187. // - Sort with directories on top.
  188. // - Treat apps as directories (MacOS and *nix only)
  189. // - Show or hide invisible files.
  190. // - Show or hide ignored files in the ignored files list (see $row_settings in code below $settings).
  191. // - Show or hide file details.
  192. // - Use custom file icons or browser defaults.
  193. // - Autoload index.ext files.
  194. // - Autoload cover art in directories with audio files.
  195. // - Text editing default view: split, source, or preview.
  196. // - Text editing sync scroll: on or off.
  197.  
  198. // ## KEYBINDINGS (These don't work in all browsers):
  199.  
  200. // - <kbd>Arrow Up/Down</kbd>: Select prev/next item.
  201. // - 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.
  202. // - <kbd>Arrow Left/Right</kbd>: Select prev/next row of the same kind as the current selection.
  203. // - If current selection is a media file, select and begin playback of the next media item.
  204. // - <kbd>Opt/Alt + Arrow Left/Right</kbd>: Skip audio ±10s
  205. // - <kbd>Opt/Alt + Shift + Arrow Left/Right</kbd>: Skip audio ±30s
  206. // - <kbd>Cmd/Ctrl + Arrow Up</kbd>: Go to parent directory
  207. // - <kbd>Cmd/Ctrl + Arrow Down</kbd>: Open selected directory
  208. // - <kbd>Return</kbd>: Open selected directory, select file, or pause/play media.
  209. // - <kbd>Space</kbd>: Pause/Play media files
  210. // - <kbd>Cmd/Ctrl + D</kbd>: Toggle file details (size, date modified) in some index page types.
  211. // - <kbd>Cmd/Ctrl + E</kbd>: Show text editor.
  212. // - <kbd>Cmd/Ctrl + G</kbd>: Show or Reset Grid.
  213. // - <kbd>Cmd/Ctrl + I</kbd>: Toggle Invisibles.
  214. // - <kbd>Cmd/Ctrl + Shift + O</kbd>: Open selected item in new window/tab.
  215. // - <kbd>Cmd/Ctrl + R</kbd>: Reload grids and previewed content, reset scaled images/fonts, reset media files to beginning.
  216. // - <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.
  217. // - <kbd>Cmd/Ctrl + Shift + < or ></kbd>: Scale preview items and grids.
  218.  
  219. // CHANGELOG:
  220.  
  221. // **VERSION 4.1.1**
  222. // A few small fixes and style tweaks.
  223.  
  224. // **VERSION 4.1.0**
  225. // **NEW:** Basic support for media playlists (.m3u and .m3u8).
  226. // - Added "Open Playlist..." item to the main menu.
  227. // - Playlist items will replace the current directory items in the sidebar. Times (if available) will be displayed in the "size" column. "Default" sorting = original playlist sort.
  228. // - Playlist can be closed via the "Close" button or shortcut, and the previous directory contents will be loaded.
  229. // - Streaming links are not supported.
  230. // - Beware of cross-origin limitations. For example, if your playlist includes locally-hosted media files, you will need to load it from a file:/// page in your browser.
  231. // - For remote files, if you are using a javascript-blocker (like uMatrix or NoScript), you may have to allow scripts from the hosting site (e.g., archive.org) in order for playback to work.
  232. // **NEW:** Open local fonts directly and view font information and complete glyph repertoire. (The previous ability to browse fonts in the directory list is unchanged.)
  233. // - Added "Open Font..." item in the menu item.
  234. // - View individual glyphs and save as SVG.
  235. // **FIXED:** Apps weren't being properly classified in the index.
  236. // **FIXED:** An issue with formatting the current directory name in the sidebar header.
  237. // **IMPROVED:** Refreshed UI colors and icons; added icons for more file types.
  238. // **IMPROVED:** Many styling adjustments, including setting numbers (for sizes and date, etc.) to tabular spacing.
  239. // **CHANGED:** Renamed "shortcuts" user setting to "bookmarks"; if you use exported settings, you'll have to change this in your code.
  240. // **INTERNALS:** Prettified and modularized some code. Removed some newly-unnecessary functions. Began to prune CSS.
  241. // - Updated markdown-it to 9.1.0.
  242.  
  243. // ***** GENERAL SETUP ***** //
  244.  
  245. // ************************************ //
  246. // DON'T EDIT ANYTHING BELOW THIS LINE. //
  247. // ************************************ //
  248.  
  249. // PATHS
  250. // Fix "%" error in file name; see https://stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser
  251. function decodeURIComponentSafe(s) {
  252. if ( !s ) { return s; }
  253. return decodeURIComponent(s.replace(/%(?![0-9a-fA-F]{2})/g, '%25') ); // replace % with %25 if not followed by two a-f/number
  254. }
  255.  
  256. const $protocol = window.location.protocol;
  257. const $origin = $protocol +'//'+ window.location.host;
  258. const $location = decodeURIComponentSafe( [location.protocol, '//', location.host, location.pathname].join('') );
  259. const $current_dir_path = $location.replace(/([\/|_|—])/g,'$1<wbr>').replace(/\\/g,'/'); // URL w/o query string for display
  260.  
  261. function escapeStr(str) { str = str.replace(/([\^\$\|\?\*\+\(\)\[])/g,'\$1'); }
  262.  
  263. // if URL is a file, change window location to parent dir, add querystring of file name; then autoload file.
  264. function loadFile() {
  265. if ( $location.slice($location.lastIndexOf('/')).indexOf('.') !== -1 && !$location.endsWith('/') && window.top === window.self ) {
  266. let $query_prefs = getQueryPrefs();
  267. $query_prefs.set( 'file', $location.slice($location.lastIndexOf('/') + 1) );
  268. window.location = $location.slice(0,$location.lastIndexOf('/') + 1) +'?'+ $query_prefs;
  269. return;
  270. }
  271. }
  272. loadFile();
  273.  
  274. // QUERY PREFS
  275. function getQueryPrefs() { return new URL(window.location).searchParams; }
  276. // const initialQueryPrefs = getQueryPrefs();
  277. // set query key/value
  278. function setQuery(key, value) {
  279. let $query_prefs = getQueryPrefs();
  280. $query_prefs.set( key, value );
  281. updateQuery($query_prefs);
  282. }
  283. // get query value
  284. function getQuery(key) {
  285. let $query_prefs = getQueryPrefs();
  286. let value = '';
  287. if ( key === 'width' ) {
  288. value = ( !$query_prefs.has(key) ? 30 : Math.round(100 * Number.parseInt($query_prefs.get('width'))/window.innerWidth) ); // number string
  289. } else {
  290. value = ( $query_prefs.has(key) ? $query_prefs.get(key) : $settings[key] !== undefined ? $settings[key].toString() : '' );
  291. value = value.replace('%2F','').replace('/',''); // some servers add a '/' to end of query string
  292. }
  293. return value;
  294. }
  295. // toggle query key
  296. function toggleQuery(key) {
  297. let $query_prefs = getQueryPrefs();
  298. let nonBoolPrefs = {
  299. 'theme_light': {'theme':'dark'},
  300. 'theme_dark': {'theme':'light'},
  301. 'source_text': {'default_text_view':'preview_text'},
  302. 'preview_text': {'default_text_view':'source_text'},
  303. 'sort_by_default': {'sort_by':'default'},
  304. 'sort_by_name': {'sort_by':'name'},
  305. 'sort_by_size': {'sort_by':'size'},
  306. 'sort_by_date': {'sort_by':'date'},
  307. 'sort_by_kind': {'sort_by':'kind'},
  308. 'sort_by_ext': {'sort_by':'ext'}
  309. };
  310. var value, queryValue, settingsValue;
  311. if ( nonBoolPrefs[key] !== undefined ) {
  312. value = Object.values(nonBoolPrefs[key]).toString();
  313. key = Object.keys(nonBoolPrefs[key]).toString(); // must come after value: i.e., don't redefine key before getting value
  314. if ( $settings[key] === value ) { $query_prefs.delete( key ); } else { $query_prefs.set( key, value ); }
  315. } else {
  316. queryValue = $query_prefs.get(key);
  317. settingsValue = $settings[key];
  318. value = ( queryValue === null ? settingsValue.toString() : queryValue.toString() );
  319. value = ( value === 'true' ? 'false' : 'true' );
  320. if ( ( queryValue !== null && queryValue !== settingsValue ) ) {
  321. $query_prefs.delete( key );
  322. } else {
  323. $query_prefs.set( key, value );
  324. }
  325. }
  326. updateQuery($query_prefs);
  327. }
  328. // remove query key
  329. function removeQuery(key) {
  330. let $query_prefs = getQueryPrefs();
  331. $query_prefs.delete(key);
  332. updateQuery($query_prefs);
  333. }
  334. // update query string
  335. function updateQuery(querystr) {
  336. querystr = querystr.toString().replace('%2F','').replace('/','');
  337. window.history.replaceState({}, document.title, window.location.pathname +'?'+ querystr);
  338. updateParentLinks();
  339. }
  340.  
  341. // ***** SET UP UI ELEMENTS ***** //
  342.  
  343. // SIDEBAR ELEMENTS
  344. // ***** BUILD MENUS ***** //
  345. // Parent and Parents Menus
  346. function updateQueryStr(str) {
  347. str = str.replace(/([^\?]*)selected=[^&]*(.*)$/m,'$1$2') // delete current selected query, if any
  348. .replace(/([^\?]*)history=(\d+)\+*([^&]*)(&*)(.*)/m,'$1$4$5&selected=$2&history=$3').replace(/&{2,}/g,'&').replace(/\?&/m,'\?'); // format query with selected and history at end
  349. if ( str.endsWith('&history=') ) { str = str.replace(/(.+)&history=$/m,'$1'); } // if no history, delete query
  350. return str;
  351. }
  352. // create links
  353. function createParentLinks() {
  354. let $links = [];
  355. let str = decodeURIComponentSafe(window.location.search);
  356. str = str.replace('/','').replace('%2F','');
  357. let $linkPieces = $location.split('/');
  358. $linkPieces = $linkPieces.slice(2,-2); // remove beginning and ending empty elements and current directory
  359. while ( $linkPieces.length > 0 ) { // while there are link pieces...
  360. str = updateQueryStr(str); // update selected and history
  361. let link = $protocol +'//'+ $linkPieces.join('/') +'/'+ str; // assemble link
  362. $links.push(link); // add to link array
  363. $linkPieces.pop(); // remove last link piece and repeat...
  364. }
  365. return $links;
  366. }
  367. // create menu items
  368. function createParentLinkItems() {
  369. let $parent_link_menu_items = [];
  370. let $links = createParentLinks();
  371. $('#parent_dir_menu').find('a').attr( 'href', $links[0] ); // set parent link
  372. for ( let i = 0; i < $links.length; i++ ) {
  373. let display_name = $links[i].slice(0,$links[i].lastIndexOf('?') - 1);
  374. display_name = display_name.replace(/\//g,'\/<wbr>');
  375. let menu_item = '<li><a href="'+ $links[i] +'">' + display_name + '/</a></li>';
  376. $parent_link_menu_items.push(menu_item);
  377. }
  378. return $parent_link_menu_items; // return parents link items
  379. }
  380. function updateParentLinks() { $('#parents_dir_menu').siblings('ul').empty().append( createParentLinkItems() ); }
  381.  
  382. // MENUS: User bookmarks
  383. function bookmarksMenuItems() {
  384. const $bookmarks = $settings.bookmarks;
  385. let menu_items = [];
  386. let $links_arr = [];
  387. let $links_arr_str = '';
  388. let $links;
  389. if ( $bookmarks.length > 0 ) {
  390. for ( let i = 0; i < $bookmarks.length; i+=1 ) {
  391. $links = $bookmarks[i].links;
  392. // make array of links
  393. for ( let j = 0; j < $links.length; j+=1 ) {
  394. if ( !$links[j].link.endsWith('/') ) {
  395. $links[j].link = $links[j].link.slice(0,$links[j].link.lastIndexOf('/') + 1) + '?file=' + $links[j].link.slice($links[j].link.lastIndexOf('/') + 1) ;
  396. }
  397. $links_arr[j] = '<li><a href="'+ $links[j].link +'">' + $links[j].link_name + '</a></li>';
  398. }
  399. $links_arr_str = $links_arr.join('');
  400. menu_items[i] = '<li class="bookmarks has_submenu"><a>'+ $bookmarks[i].menu_title +'</a><ul>'+ $links_arr_str +'</ul></li>';
  401. }
  402. menu_items = menu_items.join('');
  403. }
  404. return menu_items;
  405. }
  406. // MENUS: Other menu items
  407. const MenuItems = function() {
  408. let 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>';
  409. let autoload_media = '<li class="toggle_UI_pref rule" id="autoload_media"><span id="autoload_media_menu">Autoload Media</span></li>';
  410. let theme = '<li><span id="theme" class="toggle_UI_pref"><span id="theme_dark">Dark Theme</span><span id="theme_light">Light Theme</span></span></li>';
  411. let alternate_background = '<li class="toggle_UI_pref" id="alternate_background"><span>Alternate Backgrounds</span></li>';
  412. let show_numbers = '<li class="toggle_UI_pref rule" id="show_numbers"><span>Show Numbers</span></li>';
  413. let 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>';
  414. let disable_text_editing = '<li class="rule"><span class="toggle_UI_pref" id="enable_text_editing">Disable Text Editing</span></li>';
  415. let open_playlist = '<li class="rule"><label id="open_playlist_label" for="open_playlist">Open Playlist&hellip;</label><input type="file" id="open_playlist" name="open_playlist" accept=".m3u,.m3u8"></input></li>';
  416. let open_font = '<li class="rule"><label id="open_font_label" for="open_font">Open Font&hellip;</label><input type="file" id="open_font" name="open_font" accept=".otf,.ttf,.woff"></input></li>';
  417. let default_settings = '<li><a href="#" id="default_settings" title="Delete URL query string and reload page.">Default User Settings</a></li>';
  418. let export_settings = '<li class="rule"><a href="#" id="export_settings" title="Export user settings to text file.">Export User Settings</a></li>';
  419. let contact_link = '<li><a id="contact" href="mailto:mshroud@vivaldi.net">Contact</a></li>';
  420. let donate_link = '<li><a id="donate" href="https://paypal.me/mschrauzer" target="_blank">Donate</a></li>';
  421. return sort_by + theme + alternate_background + show_numbers + autoload_media + text_editing + disable_text_editing + open_playlist + open_font + default_settings + export_settings + contact_link + donate_link;
  422. };
  423. const SidebarHeaderEls = function() {
  424. let parent_link_items = createParentLinkItems();
  425. let parent_link = $(parent_link_items[0]).find('a').attr('href');
  426. parent_link_items = parent_link_items.toString().replace(/<\/li>,<li>/g,'</li><li>');
  427. let parent_dir_menu = '<nav id="parent_dir_menu"><a href="'+ parent_link +'">&nbsp;</a></nav>';
  428. let parents_dir_menu = '<nav id="parents_dir_menu"><div>'+ $current_dir_path +'</div></nav><ul class="menu">'+ parent_link_items +'</ul>';
  429. let bookmarks_menu = '<nav id="bookmarks_menu"><div>&nbsp;</div></nav><ul id="bookmarks" class="menu">'+ bookmarksMenuItems() + MenuItems() +'</ul>';
  430. let show_details = '<button class="toggle_UI_pref" id="show_details" tabindex="-1"><span>Show details</span><span>Hide details</span></button>';
  431. let inv_checkbox = '<label><input class="toggle_UI_pref" type="checkbox" id="show_invisibles" for="inv_checkbox" name="inv_checkbox" tabindex="-1" />Show Invisibles</label>';
  432. let grid_btn = '<td 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></td>';
  433. let sorting = '<tr class="header"><th id="sorting" colspan="4"><div><div class="toggle_UI_pref name sorting" id="sort_by_name" colspan="2"><span><input id="play_toggle" type="checkbox" tabindex="-1" checked="true" />Name</span></div><div class="toggle_UI_pref sorting" id="sort_by_default" colspan="2"><span>Default</span></div><div class="toggle_UI_pref details sorting" id="sort_by_ext"><span>Ext</span></div><div class="toggle_UI_pref details sorting" id="sort_by_size"><span>Size</span></div><div class="toggle_UI_pref details sorting" id="sort_by_date"><span>Date</span></div><div class="toggle_UI_pref details sorting" id="sort_by_kind"><span>Kind</span></div></div></th></tr>';
  434. let text_editor_row = '<tr id="text_editor_row"><th colspan="4"><a href="#" title="Toggle Text Editor">Text Editor</a></th></tr>';
  435. let sidebar_header = '<table id="sidebar_header"><thead><tr id="sidebar_title"><th colspan="4">INDEX OF</th></tr></thead><tbody><tr id="sidebar_menus"><td>'+ parent_dir_menu +'</td><td colspan="2">'+ parents_dir_menu +'</td><td id="bookmarks_menu_container">'+ bookmarks_menu +'</td></tr><tr id="sidebar_buttons"><td colspan="3">'+ show_details + inv_checkbox +'</td>'+ grid_btn +'</tr>'+ sorting + text_editor_row +'</tbody></table>';
  436. let sidebar_header_els = sidebar_header;
  437. return sidebar_header_els;
  438. };
  439. // Dir List Elements
  440. const SidebarDirListEls = function() {
  441. let dir_list_body = '<tbody id="tbody"></tbody>';
  442. let dir_list_foot = '<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>';
  443. let sidebar_dir_list_els = '<table id="dir_list">'+ dir_list_body + dir_list_foot +'</table>';
  444. return sidebar_dir_list_els;
  445. };
  446. // CONTENT PANE ELEMENTS
  447. const ContentHeaderEls = function() {
  448. let title_buttons_left = '<td id="title_buttons_left"><button id="reload_btn" tabindex="-1"><span>Reload</span></button><button id="prev_next_btns" class="split_btn"><span id="prev_btn"><span>&nbsp;</span></span><span id="next_btn"><span>&nbsp;</span></span></button></td>';
  449. let title = '<td id="title"></td>';
  450. let 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"><span>Close</span></button></td>';
  451. let content_title = '<tr id="content_title">'+ title_buttons_left + title + title_buttons_right +'</tr>';
  452. let content_header_els = '<header id="content_header"><table><tbody>'+ content_title + ContentAudioEls() +'</tbody></table></header>';
  453. return content_header_els;
  454. };
  455. // Content containers
  456. const ContentEls = function() {
  457. let content_grid = '<div id="content_grid" data-grid-scale-factor="1" data-kind="grid"></div>';
  458. let content_text = '<div id="content_text"></div>';
  459. let content_font = ContentFontEls();
  460. let content_image = '<img id="content_image" class="content" data-kind="image" />';
  461. let content_video = '<video id="content_video" class="content media" controls data-kind="video">Your browser does not support the video tag.</video>';
  462. let content_pdf = '<embed id="content_pdf" class="content" name="plugin" tabindex="0" data-kind="pdf"></embed>';
  463. let content_iframe = '<iframe id="content_iframe" class="content" name="content_iframe" sandbox="allow-scripts allow-same-origin allow-modals" tabindex="0" data-kind="file"></iframe>';
  464. let content_els = '<section id="content_container">'+ content_grid + content_text + content_font + content_image + content_pdf + content_video + content_iframe +'</section>';
  465. return content_els;
  466. };
  467. // Content Audio Els
  468. const ContentAudioEls = function() {
  469. let prev_track = '<div id="prev_track" class="prev_next_track_btn" title="Previous track">&nbsp;</div>';
  470. let next_track = '<div id="next_track" class="prev_next_track_btn" title="Next track">&nbsp;</div>';
  471. let audio_player = '<audio id="audio" preload="auto" tabindex="0" controls >Sorry, your browser does not support HTML5 audio.</audio>';
  472. let loop = '<label><input type="checkbox" id="loop" for="loop" name="loop" tabindex="0" />Loop</label>';
  473. let shuffle = '<label><input type="checkbox" id="shuffle" for="shuffle" name="shuffle" tabindex="0" />Shuffle</label>';
  474. let checkbox_cont = '<div id="checkbox_div">'+ loop + shuffle +'</div>';
  475. let close_audio = '<div id="close_audio" title="Close audio"></div>';
  476. let content_audio_title = '<tr id="content_audio_title"><td colspan="3"></td></tr>';
  477. let content_audio_els = content_audio_title +'<tr id="content_audio"><td colspan="3"><div id="audio_container">'+ prev_track + next_track + audio_player + close_audio +'</div>'+ checkbox_cont +'</td></tr>';
  478. return content_audio_els;
  479. };
  480. // Content Font Els
  481. const ContentFontEls = function() {
  482. let sample_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789 [(!@#$%^&*;:)]';
  483. let specimen = '<div class="specimen" contenteditable="true" style="font-size:4em;word-break: break-all;line-height: 1.2;">'+ sample_string +'</div>';
  484. let hamburger_string = '<h1 style="font-size:8em">Typography</h1><h4 style="font-size:1.618em">The art of using types to produce impressions on paper, vellum, &amp;c.</h4><h2 style="font-size:6em;text-align:justify;">S P E C I M E N</h2><h3 style="font-size:2em">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>';
  485. let lorem_string = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
  486. let lorem = '<div class="lorem" style="text-align:justify;font-size:1em;line-height: 1.4;column-gap:1.5em;overflow-wrap: normal;word-break: normal;" contenteditable="true">'+ lorem_string +'</div>';
  487. let lorem2 = lorem.replace('style="','style="columns:2;');
  488. let lorem3 = lorem.replace('style="','style="columns:3;');
  489. let hamburger = '<div class="hamburger" style="text-align:justify" contenteditable="true">'+ hamburger_string +'</div>'+ lorem.toString() + lorem2.toString() + lorem3.toString() +'</div>';
  490. let content_font = '<div id="content_font" class="content" spellcheck="false" data-kind="font">'+ specimen + hamburger +'</div>';
  491. return content_font;
  492. };
  493. // ASSEMBLE SIBEBAR & CONTENT PANE ELEMENTS
  494. const MainContent = function() {
  495. let handle = '<div id="handle"></div>';
  496. let toggle_sidebar = '<div id="toggle_sidebar"></div>';
  497. let 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>';
  498. let overlay = '<div id="overlay"></div>';
  499. let sidebar = '<div id="sidebar">'+ SidebarHeaderEls() + SidebarDirListEls() +'</div>';
  500. let sidebar_wrapper = '<td id="sidebar_wrapper">'+ sidebar + handle + toggle_sidebar +'</td>';
  501. let content_pane = '<td id="content_pane" class="">'+ ContentHeaderEls() + ContentEls() +'</td>';
  502.  
  503. let main_head = '<thead><tr><th>'+ warnings + overlay +'</th></tr></thead>';
  504. let main_body = '<tbody><tr>'+ sidebar_wrapper + content_pane +'</tr></tbody>';
  505. let main_foot = '<tfoot></tfoot>';
  506. let main_content = '<table id="main_content">'+ main_head + main_body + main_foot +'</table>';
  507. return $(main_content);
  508. };
  509. // DEFINE Content Elements
  510. const $main_content = MainContent();
  511. // DIR LIST
  512. const $dir_list_body = $main_content.find('#tbody');
  513. const $dir_list = $main_content.find('#dir_list');
  514. // CONTENT
  515. const $content_pane = $main_content.find('#content_pane');
  516. const $audio_player = $main_content.find('#audio');
  517. const $content_text = $main_content.find('#content_text');
  518. const $content_grid = $main_content.find('#content_grid');
  519. const $content_font = $main_content.find('#content_font');
  520. const $content_image = $main_content.find('#content_image');
  521. const $content_pdf = $main_content.find('#content_pdf');
  522. const $content_video = $main_content.find('#content_video');
  523. const $content_iframe = $main_content.find('#content_iframe');
  524.  
  525. // SVG UI ICONS
  526. function SVG_UI_Icon(icon_name) {
  527. let svg = '';
  528. if ( icon_name.endsWith('_dark') ) {
  529. icon_name = icon_name.slice(0,-5);
  530. svg = SVG_UI_Icons[icon_name];
  531. svg = svg.replace(/4{6}/g,'BBBBBB');
  532. } else {
  533. svg = SVG_UI_Icons[icon_name];
  534. }
  535. return 'url("data:image/svg+xml;utf8,'+ svg +'")';
  536. }
  537. const SVG_UI_Icons = {
  538. 'arrow': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M4 4l12 6-12 6z\' /></svg>',
  539. 'bookmark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4V2zm2 0v15l6-3 6 3V2H4z\' /></svg>',
  540. 'check_mark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 12 09\'><g transform=\'matrix(0.55,5.55112e-17,-5.55112e-17,0.55,0.578932,-1.01245)\'><path d=\'M-0.071,10.929L2.5,8.358L7,12.857L17.285,2.572L19.856,5.144L7,18L-0.071,10.929Z\' style=\'fill:rgb(68,68,68);fill-rule:nonzero;\'/></g></svg>',
  541. 'chevron_up': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 12 08\'><g transform=\'matrix(1,0,0,1,-3.843,-5.843)\'><path d=\'M10.707,7.05L10,6.343L4.343,12L5.757,13.414L10,9.172L14.243,13.414L15.657,12L10.707,7.05Z\' style=\'fill:rgb(68,68,68);fill-rule:nonzero;\'/></g></svg>',
  542. 'chevron_right': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 08 12\'><g transform=\'matrix(1,0,0,1,-6.086,-4)\'><path d=\'M12.95,10.707L13.657,10L8,4.343L6.586,5.757L10.828,10L6.586,14.243L8,15.657L12.95,10.707Z\' style=\'fill:rgb(68,68,68);fill-rule:nonzero;\'/></g></svg>',
  543. 'chevron_down': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 12 08\'><g transform=\'matrix(1,0,0,1,-4,-6.157)\'><path d=\'M9.293,12.95L10,13.657L15.657,8L14.243,6.586L10,10.828L5.757,6.586L4.343,8L9.293,12.95Z\' style=\'fill:rgb(68,68,68);fill-rule:nonzero;\'/></g></svg>',
  544. 'chevron_left': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 08 12\'><g transform=\'matrix(1,0,0,1,-5.843,-4)\'><path d=\'M7.05,9.293L6.343,10L12,15.657L13.414,14.243L9.172,10L13.414,5.757L12,4.343L7.05,9.293Z\' style=\'fill:rgb(68,68,68);fill-rule:nonzero;\'/></g></svg>',
  545. 'document': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M4 18h12V6h-4V2H4v16zm-2 1V0h12l4 4v16H2v-1z\' /></svg>',
  546. 'error': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23FFB636\' id=\'Layer_2\' d=\'M1.075,18.05l8.146,-16.683c0.236,-0.484 0.924,-0.491 1.169,-0.011l8.537,16.683c0.223,0.435 -0.093,0.952 -0.582,0.952l-16.683,0c-0.483,0 -0.799,-0.507 -0.587,-0.941Z\' style=\'fill-opacity:0.75;fill-rule:nonzero;\'/><path id=\'Layer_3\' d=\'M11.055,7.131l-0.447,6.003c-0.034,0.45 -0.425,0.787 -0.874,0.753c-0.408,-0.03 -0.724,-0.356 -0.753,-0.753l-0.447,-6.003c-0.052,-0.696 0.47,-1.302 1.167,-1.354c0.696,-0.052 1.302,0.47 1.354,1.166c0.005,0.061 0.004,0.129 0,0.188Zm-1.26,8.037c-0.641,0 -1.159,0.518 -1.159,1.158c0,0.641 0.518,1.159 1.159,1.159c0.64,0 1.158,-0.518 1.158,-1.159c0,-0.64 -0.518,-1.158 -1.158,-1.158Z\' style=\'fill:%23444;fill-opacity:0.75;fill-rule:nonzero;\'/></svg>',
  547. 'folder': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M0 4c0-1.1.9-2 2-2h7l2 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm2 2v10h16V6H2z\' /></svg>',
  548. 'grid': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  549. 'ignored': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M0 10a10 10 0 1 1 20 0 10 10 0 0 1-20 0zm16.32-4.9L5.09 16.31A8 8 0 0 0 16.32 5.09zm-1.41-1.42A8 8 0 0 0 3.68 14.91L14.91 3.68z\' opacity=\'0.25\' /></svg>',
  550. 'menu': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z\' /></svg>',
  551. 'minus': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><rect fill=\'%23444444\' x=\'1\' y=\'8\' width=\'18\' height=\'4\' /></svg>',
  552. 'multiply': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M10,7l6,-6l3,3l-6,6l6,6l-3,3l-6,-6l-6,6l-3,-3l6,-6l-6,-6l3,-3l6,6Z\'/></svg>',
  553. 'music': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23888888\' d=\'M15.987,13.982c0,0.906 -0.413,1.664 -1.239,2.274c-0.757,0.554 -1.604,0.831 -2.541,0.831c-0.548,0 -0.998,-0.129 -1.348,-0.388c-0.389,-0.295 -0.583,-0.708 -0.583,-1.238c0,-0.838 0.398,-1.574 1.192,-2.209c0.752,-0.597 1.559,-0.896 2.421,-0.896c0.727,0 1.257,0.145 1.59,0.434l0,-9.489l-6.755,1.82l0,10.774c0,0.906 -0.413,1.663 -1.238,2.273c-0.758,0.555 -1.605,0.832 -2.541,0.832c-0.549,0 -0.998,-0.13 -1.35,-0.388c-0.388,-0.296 -0.582,-0.709 -0.582,-1.238c0,-0.838 0.398,-1.574 1.192,-2.209c0.752,-0.597 1.559,-0.896 2.421,-0.896c0.727,0 1.257,0.145 1.589,0.434l0,-11.605l7.772,-2.098l0,12.982Z\' style=\'fill-opacity:0.4;fill-rule:nonzero;\'/></svg>',
  554. 'plus': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M8.001,1l3.999,0l0,7l7,0l0,4l-7,0l-0.001,7l-3.999,0l0,-7l-7,0l0,-4l7,0l0.001,-7Z\'/></svg>',
  555. 'prev_next_track': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M13,5l2,0l0,10l-2,0l0,-10Zm-8,0l8,5l-8,5l0,-10Z\'/></svg>',
  556. 'prev_next_track_ff': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23FFFFFF\' d=\'M12.8,14l-10.8,-7l10.8,-7l0,14Z\'\'/><rect x=\'0\' y=\'0\' width=\'2\' height=\'14\' style=\'fill:%23fff;\'/></svg>',
  557. 'spinner': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 40 40\'><g><g transform=\'translate(1 1)\'><circle style=\'fill:none;stroke:%23666666;stroke-width:4;stroke-opacity:0.5;\' cx=\'18\' cy=\'18\' r=\'18\'/><path style=\'fill:none;stroke:%23AAAAAA;stroke-width:4;\' d=\'M36,18c0-9.94-8.061-18-18-18 \'><animateTransform type=\'rotate\' fill=\'remove\' repeatCount=\'indefinite\' attributeName=\'transform\' restart=\'always\' dur=\'1s\' from=\'0 18 18\' calcMode=\'linear\' to=\'360 18 18\' accumulate=\'none\' additive=\'replace\'></animateTransform></path></g></g></svg>',
  558. 'toggle': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23444444\' d=\'M10.207,9.293l-0.707,0.707l5.657,5.657l1.414,-1.414l-4.242,-4.243l4.242,-4.243l-1.414,-1.414l-4.95,4.95Z\' /><path fill=\'%23444444\' d=\'M4.207,9.293l-0.707,0.707l5.657,5.657l1.414,-1.414l-4.242,-4.243l4.242,-4.243l-1.414,-1.414l-4.95,4.95Z\'/></svg>',
  559. };
  560.  
  561. function SVG_UI_File_Icon(icon_name) {
  562. if ( icon_name === 'file_icon_dir_default' ) { // default chrome icons
  563. return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAd5JREFUeNqMU79rFUEQ/vbuodFEEkzAImBpkUabFP4ldpaJhZXYm/RiZWsv/hkWFglBUyTIgyAIIfgIRjHv3r39MePM7N3LcbxAFvZ2b2bn22/mm3XMjF+HL3YW7q28YSIw8mBKoBihhhgCsoORot9d3/ywg3YowMXwNde/PzGnk2vn6PitrT+/PGeNaecg4+qNY3D43vy16A5wDDd4Aqg/ngmrjl/GoN0U5V1QquHQG3q+TPDVhVwyBffcmQGJmSVfyZk7R3SngI4JKfwDJ2+05zIg8gbiereTZRHhJ5KCMOwDFLjhoBTn2g0ghagfKeIYJDPFyibJVBtTREwq60SpYvh5++PpwatHsxSm9QRLSQpEVSd7/TYJUb49TX7gztpjjEffnoVw66+Ytovs14Yp7HaKmUXeX9rKUoMoLNW3srqI5fWn8JejrVkK0QcrkFLOgS39yoKUQe292WJ1guUHG8K2o8K00oO1BTvXoW4yasclUTgZYJY9aFNfAThX5CZRmczAV52oAPoupHhWRIUUAOoyUIlYVaAa/VbLbyiZUiyFbjQFNwiZQSGl4IDy9sO5Wrty0QLKhdZPxmgGcDo8ejn+c/6eiK9poz15Kw7Dr/vN/z6W7q++091/AQYA5mZ8GYJ9K0AAAAAASUVORK5CYII= ")';
  564. } else if ( icon_name === 'file_icon_file_default' ) {
  565. return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
  566. } else { // custom icons
  567. return 'url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons[icon_name] +'")';
  568. }
  569. }
  570. const SVG_UI_File_Icons = {
  571. 'file_icon_alias': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><path d=\'M3,12.5c0,-3.863 2.253,-7.5 6.259,-7.5\' style=\'fill:none;stroke:%23fc6;stroke-width:3px;\'/><path d=\'M13,5l-4,-4l0,8l4,-4Z\' style=\'fill:%23fc6;\'/></svg>',
  572. 'file_icon_archive': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M11,8.5l0,-1l2,0l0,2l-5,1l-2,0l0,1.5l4,0l0,1l-4,0l0,1l-3,0l0,-1l-2,0l0,-1l2,0l0,-1.5l-2,0l0,-2l2,0l0,-6.5l-2,0l0,-2l7,0l5,1l0,2l-2,0l0,-1l-5,0l0,6.5l5,0Z\' style=\'fill:%23999;\'/></svg>',
  573. 'file_icon_app': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path id=\'rect6894\' d=\'M6.125,0l-0.292,1.859c-0.587,0.135 -1.146,0.38 -1.64,0.693l0,-0.018l-1.532,-1.094l-1.221,1.221l1.094,1.532l0.018,0c-0.313,0.495 -0.559,1.051 -0.693,1.64l-1.859,0.292l0,1.75l1.859,0.292c0.134,0.589 0.38,1.145 0.693,1.64l-0.018,0l-1.094,1.532l1.221,1.221l1.532,-1.094l0,-0.018c0.494,0.313 1.053,0.558 1.64,0.693l0.292,1.859l1.75,0l0.292,-1.859c0.596,-0.137 1.14,-0.372 1.64,-0.693l1.532,1.112l1.221,-1.221l-1.112,-1.532c0.309,-0.492 0.523,-1.057 0.656,-1.64l1.896,-0.292l0,-1.75l-1.896,-0.292c-0.133,-0.583 -0.347,-1.148 -0.656,-1.64l0.018,0l1.094,-1.532l-1.221,-1.221l-1.532,1.094l0,0.018c-0.5,-0.321 -1.044,-0.556 -1.64,-0.693l-0.292,-1.859l-1.75,0Zm0.875,4.667c1.288,0 2.333,1.036 2.333,2.333c0,1.297 -1.045,2.333 -2.333,2.333c-1.288,0 -2.333,-1.036 -2.333,-2.333c0,-1.297 1.045,-2.333 2.333,-2.333Z\' style=\'fill:%237a7ab8;\'/></svg>',
  574. 'file_icon_audio': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><g id=\'Layer1\'><circle cx=\'7\' cy=\'7\' r=\'7\' style=\'fill:%230f8a8a;\'/></g><path d=\'M11,9.5l0,0c-0.019,0.681 -0.796,1.339 -1.75,1.475c-0.966,0.138 -1.75,-0.31 -1.75,-1c0,-0.69 0.784,-1.362 1.75,-1.5c0.268,-0.038 0.523,-0.031 0.75,0.013l0,-4.488l-4,0l0,6.5l0,0c-0.019,0.681 -0.796,1.339 -1.75,1.475c-0.966,0.138 -1.75,-0.31 -1.75,-1c0,-0.69 0.784,-1.362 1.75,-1.5c0.268,-0.038 0.523,-0.031 0.75,0.013l0,-6.488l6,-1l0,7.5Z\' style=\'fill:%23fff;\'/><path d=\'M11,2l-6,1l0,2l6,-1l0,-2Z\' style=\'fill:%23fff;\'/></svg>',
  575. 'file_icon_binary': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><g><path d=\'M1.247,6.495l3.263,0l0,-1.067l-0.881,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.052,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M7,6.588c1.082,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.743,-2.521 -1.825,-2.521c-1.082,0 -1.825,0.843 -1.825,2.521c0,1.677 0.743,2.567 1.825,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M9.598,6.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.283,0.479l0,0.82l0.927,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M2.825,12.588c1.082,0 1.824,-0.89 1.824,-2.567c0,-1.67 -0.742,-2.521 -1.824,-2.521c-1.083,0 -1.825,0.843 -1.825,2.521c0,1.677 0.742,2.567 1.825,2.567Zm0,-1.021c-0.31,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.262,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M5.423,12.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M11.175,12.588c1.083,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.742,-2.521 -1.825,-2.521c-1.082,0 -1.824,0.843 -1.824,2.521c0,1.677 0.742,2.567 1.824,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.31,0 0.572,0.201 0.572,1.5c0,1.299 -0.262,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></g></svg>',
  576. 'file_icon_code': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,0l-14,0l0,14l14,0l0,-14Z\' style=\'fill:%2372d;fill-rule:nonzero;\'/><g><path d=\'M5.923,12.965c-1.049,0 -1.784,-0.161 -2.209,-0.48c-0.425,-0.317 -0.638,-0.82 -0.638,-1.503l0,-2.067c0,-0.446 -0.146,-0.764 -0.438,-0.95c-0.292,-0.188 -0.709,-0.281 -1.256,-0.281l0,-1.368c0.547,0 0.967,-0.094 1.259,-0.28c0.292,-0.186 0.438,-0.5 0.438,-0.938l0,-2.092c0,-0.675 0.217,-1.172 0.65,-1.491c0.432,-0.32 1.164,-0.479 2.195,-0.479l0,1.312c-0.401,0.01 -0.718,0.09 -0.952,0.24c-0.233,0.15 -0.348,0.426 -0.348,0.827l0,1.985c0,0.876 -0.511,1.396 -1.532,1.559l0,0.083c1.021,0.154 1.532,0.67 1.532,1.544l0,1.997c0,0.41 0.116,0.688 0.349,0.835c0.233,0.146 0.55,0.223 0.951,0.232l-0.001,1.315Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/><path d=\'M8.076,12.965l0,-1.313c0.392,-0.009 0.706,-0.089 0.944,-0.239c0.236,-0.15 0.355,-0.426 0.355,-0.829l0,-1.996c0,-0.867 0.511,-1.382 1.531,-1.545l0,-0.084c-1.02,-0.164 -1.53,-0.679 -1.53,-1.546l0,-1.997c0,-0.41 -0.116,-0.688 -0.349,-0.834c-0.232,-0.146 -0.549,-0.224 -0.951,-0.233l0,-1.313c1.049,0 1.785,0.159 2.21,0.479c0.423,0.319 0.637,0.821 0.637,1.505l0,2.065c0,0.447 0.146,0.765 0.438,0.951c0.292,0.187 0.711,0.28 1.257,0.28l0,1.367c-0.546,0.012 -0.967,0.107 -1.259,0.287c-0.293,0.183 -0.438,0.5 -0.438,0.945l0,2.08c0,0.674 -0.217,1.172 -0.65,1.491c-0.432,0.319 -1.165,0.479 -2.195,0.479Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/></g></svg>',
  577. 'file_icon_database': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,2.5l0,9c0,1.38 -3.137,2.5 -7,2.5c-3.863,0 -7,-1.12 -7,-2.5l0,-9\' style=\'fill:%23808080;\'/><path d=\'M13,2.5l0,9c0,0.828 -2.689,1.5 -6,1.5c-3.311,0 -6,-0.672 -6,-1.5l0,-9\' style=\'fill:%23b4b4b4;\'/><path d=\'M14,8.5c0,1.38 -3.137,2.5 -7,2.5c-3.863,0 -7,-1.12 -7,-2.5\' style=\'fill:%23808080;\'/><path d=\'M13,8.5c0,0.828 -2.689,1.5 -6,1.5c-3.311,0 -6,-0.672 -6,-1.5\' style=\'fill:%23b4b4b4;\'/><path d=\'M14,5.5c0,1.38 -3.137,2.5 -7,2.5c-3.863,0 -7,-1.12 -7,-2.5\' style=\'fill:%23808080;\'/><path d=\'M13,5.5c0,0.828 -2.689,1.5 -6,1.5c-3.311,0 -6,-0.672 -6,-1.5\' style=\'fill:%23b4b4b4;\'/><ellipse cx=\'7\' cy=\'2.5\' rx=\'7\' ry=\'2.5\' style=\'fill:%23808080;\'/><ellipse cx=\'7\' cy=\'2.5\' rx=\'5.5\' ry=\'1.5\' style=\'fill:%23b4b4b4;\'/></svg>',
  578. 'file_icon_dir': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M6,2.5l-1,-1.5l-5,0l0,12l14,0l0,-10.5l-8,0Z\' style=\'fill:%2339f;fill-rule:nonzero;\'/><rect x=\'1.5\' y=\'4\' width=\'11\' height=\'7.5\' style=\'fill:%239cf;\'/></svg>',
  579. 'file_icon_dir_invisible': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M6,2.5l-1,-1.5l-5,0l0,12l14,0l0,-10.5l-8,0Z\' style=\'fill:%23888;fill-rule:nonzero;\'/><rect x=\'1.5\' y=\'4\' width=\'11\' height=\'7.5\' style=\'fill:%23bbb;\'/><circle cx=\'7\' cy=\'7.5\' r=\'1.5\' style=\'fill:%23888;\'/></svg>',
  580. 'file_icon_ebook': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M2.668,-0.001c1.705,0.001 3.492,0.35 4.332,1.257c0.84,-0.908 2.627,-1.256 4.332,-1.257l2.668,0c0,4.112 0,8.43 0,12.541c-0.818,0 -2.181,0.005 -3,0.023c-1.184,0.026 -3.008,0.42 -3,1.437l-1,-0.017l-1,0.017c0.008,-1.017 -2,-1.437 -3,-1.437c-0.819,0 -2.182,-0.023 -3,-0.023l0,-12.541l2.668,0Z\' style=\'fill:%23808080;\'/><path d=\'M1.5,1.499l0,9.501l1.286,0c1.086,0.025 2.213,0.081 3.204,0.568l0.01,0.006c0,-2.859 0,-5.717 0,-8.576c0,-1.136 -1.49,-1.398 -2.336,-1.47c-0.708,-0.059 -1.438,-0.029 -2.164,-0.029Z\' style=\'fill:%23cdcdcd;\'/><path d=\'M12.5,1.499l0,9.501l-1.286,0c-1.086,0.025 -2.213,0.081 -3.204,0.568l-0.01,0.006c0,-2.859 0,-5.717 0,-8.576c0,-1.136 1.49,-1.398 2.336,-1.47c0.708,-0.059 1.438,-0.029 2.164,-0.029Z\' style=\'fill:%23cdcdcd;\'/></svg>',
  581. 'file_icon_file': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><g><path d=\'M8.3,0l-6.8,0l0,14l11,0l0,-9.8l-4.2,-4.2Z\' style=\'fill:%23888;fill-rule:nonzero;\'/><path d=\'M11,12.5l-8,0l0,-11l3.8,0l0,4.2l4.2,0l0,6.8Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/><path d=\'M8.3,4.2l1.9,0l-1.9,-2l0,2Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/></g></svg>',
  582. 'file_icon_invisible': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><g><path d=\'M8.3,0l-6.8,0l0,14l11,0l0,-9.8l-4.2,-4.2Z\' style=\'fill:%23888;fill-rule:nonzero;\'/><path d=\'M11,12.5l-8,0l0,-11l3.8,0l0,4.2l4.2,0l0,6.8Z\' style=\'fill:%23bbb;fill-rule:nonzero;\'/><path d=\'M8.3,4.2l1.9,0l-1.9,-2l0,2Z\' style=\'fill:%23bbb;fill-rule:nonzero;\'/></g><circle cx=\'7\' cy=\'9\' r=\'1.5\' style=\'fill:%23878787;\'/></svg>',
  583. 'file_icon_font': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,0l-14,0l0,14l14,0l0,-14Z\' style=\'fill:%23709;fill-rule:nonzero;\'/><path d=\'M4.678,11.179l1.393,0l0,-8.266l-2.616,0l0,1.052l-1.455,0l0,-2.553l10,0l0,2.554l-1.456,0l0,-1.053l-2.599,0l0,8.266l1.347,0l0,1.409l-4.614,0l0,-1.409Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/></svg>',
  584. 'file_icon_graphics': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><path d=\'M7.774,8.285l4.726,4.715l-8,-3.525l-1.5,-4.975l-2,0l0,-3.5l3.525,0l-0.025,2l5,1.5l3.5,8l-4.7,-4.752c0.127,-0.22 0.2,-0.476 0.2,-0.748c0,-0.828 -0.672,-1.5 -1.5,-1.5c-0.828,0 -1.5,0.672 -1.5,1.5c0,0.828 0.672,1.5 1.5,1.5c0.283,0 0.548,-0.079 0.774,-0.215Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></svg>',
  585. 'file_icon_htm': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M6.967,0.5c-3.553,0.018 -6.467,2.947 -6.467,6.5c0,3.566 2.934,6.5 6.5,6.5c3.566,0 6.5,-2.934 6.5,-6.5c0,-3.553 -2.914,-6.482 -6.467,-6.5l-0.066,0Zm0.033,0l0,13m6.5,-6.5l-13,0m1.467,-4c3.004,2.143 7.062,2.143 10.066,0m0,8c-3.004,-2.143 -7.062,-2.143 -10.066,0m4.533,-10.333c-1.874,1.582 -2.957,3.914 -2.957,6.366c0,2.453 1.083,4.785 2.957,6.367m1,0c1.874,-1.582 2.957,-3.914 2.957,-6.367c0,-2.452 -1.083,-4.784 -2.957,-6.366\' style=\'fill:%23fff;fill-rule:nonzero;stroke:%23E44D26;stroke-width:1px;\'/></svg>',
  586. 'file_icon_ignored': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M7,0c-3.9,0 -7,3.1 -7,7c0,3.9 3.1,7 7,7c3.9,0 7,-3.1 7,-7c0,-3.9 -3.1,-7 -7,-7Z\' style=\'fill:%23999;fill-rule:nonzero;\'/><path d=\'M7,2c2.8,0 5,2.2 5,5c0,2.8 -2.2,5 -5,5c-2.8,0 -5,-2.2 -5,-5c0,-2.8 2.2,-5 5,-5\' style=\'fill:%23ddd;fill-rule:nonzero;\'/><path d=\'M10.695,1.774l-8.839,8.839l1.626,1.626l8.839,-8.839l-1.626,-1.626Z\' style=\'fill:%23999;\'/></svg>',
  587. 'file_icon_image': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M0.369,9.141c-0.252,-0.678 -0.369,-1.396 -0.369,-2.141c0,-3.863 3.137,-7 7,-7c3.863,0 7,3.137 7,7l-0.137,1.353l-3.853,-3.853l-3.5,3.5l-2.5,-2.5l-3.641,3.641Z\' style=\'fill:%238080ff;\'/><path d=\'M0.839,10.151l-0.47,-1.01l3.641,-3.641l2.5,2.5l3.5,-3.5l3.853,3.853c-0.076,0.395 -0.201,0.778 -0.341,1.147l-10.371,3.345c-0.293,-0.194 -0.579,-0.416 -0.838,-0.651l-1.474,-2.043Z\' style=\'fill:%23fff;\'/><g><path d=\'M13.522,9.5c-0.99,2.64 -3.539,4.5 -6.522,4.5c-1.426,0 -2.753,-0.421 -3.849,-1.155l6.859,-6.866l3.512,3.521Z\' style=\'fill:%2333c;\'/><path d=\'M0.839,10.151l3.171,-3.172l1.761,1.761l-3.459,3.454c-0.591,-0.632 -1.079,-1.313 -1.473,-2.043Z\' style=\'fill:%2333c;\'/></g><circle cx=\'6\' cy=\'3.5\' r=\'1.5\' style=\'fill:%23fff;\'/></svg>',
  588. 'file_icon_ignored_image': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M0.369,9.141c-0.252,-0.678 -0.369,-1.396 -0.369,-2.141c0,-3.863 3.137,-7 7,-7c3.863,0 7,3.137 7,7l-0.137,1.353l-3.853,-3.853l-3.5,3.5l-2.5,-2.5l-3.641,3.641Z\' style=\'fill:%23808080;\'/><path d=\'M0.839,10.151l-0.47,-1.01l3.641,-3.641l2.5,2.5l3.5,-3.5l3.853,3.853c-0.076,0.395 -0.201,0.778 -0.341,1.147l-10.371,3.345c-0.293,-0.194 -0.579,-0.416 -0.838,-0.651l-1.474,-2.043Z\' style=\'fill:%23fff;\'/><g><path d=\'M13.522,9.5c-0.99,2.64 -3.539,4.5 -6.522,4.5c-1.426,0 -2.753,-0.421 -3.849,-1.155l6.859,-6.866l3.512,3.521Z\' style=\'fill:%23808080;\'/><path d=\'M0.839,10.151l3.171,-3.172l1.761,1.761l-3.459,3.454c-0.591,-0.632 -1.079,-1.313 -1.473,-2.043Z\' style=\'fill:%23808080;\'/></g><circle cx=\'6\' cy=\'3.5\' r=\'1.5\' style=\'fill:%23fff;\'/></svg>',
  589. 'file_icon_markdown': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,0l-14,0l0,14l14,0l0,-14Z\' style=\'fill:%236a6a95;fill-rule:nonzero;\'/><path d=\'M12,11.5l-2.5,0l0,-5.143l-2.5,2.948l-2.5,-2.948l0,5.143l-2.5,0l0,-9l2.273,0l2.721,3.377l2.733,-3.377l2.273,0l0,9Z\' style=\'fill:%23DDD;fill-rule:nonzero;\'/></svg>',
  590. 'file_icon_office': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><rect x=\'10\' y=\'1.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'10\' y=\'4\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'10\' y=\'6.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'10\' y=\'9\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'10\' y=\'11.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'1.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'4\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'6.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'9\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'6.5\' y=\'11.5\' width=\'2.5\' height=\'1\' style=\'fill:%23cdcdcd;\'/><rect x=\'1.5\' y=\'1.5\' width=\'4\' height=\'11\' style=\'fill:%23cdcdcd;\'/></svg>',
  591. 'file_icon_pdf': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23d20000;fill-rule:nonzero;\'/><path d=\'M12.69,9.115c-0.075,0.048 -0.291,0.076 -0.428,0.076c-0.443,0 -0.99,-0.204 -1.762,-0.534c0.297,-0.022 0.568,-0.031 0.811,-0.031c0.446,0 0.575,0 1.012,0.109c0.433,0.108 0.438,0.333 0.367,0.38Zm-7.72,0.069c0.172,-0.303 0.349,-0.622 0.526,-0.961c0.44,-0.83 0.719,-1.484 0.923,-2.017c0.413,0.749 0.926,1.383 1.525,1.894c0.077,0.063 0.157,0.125 0.242,0.189c-1.224,0.246 -2.283,0.539 -3.216,0.895Zm1.428,-7.856c0.244,0 0.384,0.612 0.395,1.191c0.011,0.573 -0.121,0.974 -0.29,1.277c-0.141,-0.445 -0.205,-1.14 -0.205,-1.596c0,-0.001 -0.01,-0.872 0.1,-0.872Zm-4.788,11.025c0.142,-0.378 0.687,-1.124 1.494,-1.788c0.051,-0.038 0.177,-0.157 0.292,-0.266c-0.843,1.35 -1.412,1.885 -1.786,2.054Zm11.312,-4.029c-0.242,-0.241 -0.789,-0.367 -1.615,-0.377c-0.56,-0.008 -1.23,0.041 -1.942,0.139c-0.315,-0.184 -0.641,-0.381 -0.9,-0.622c-0.689,-0.646 -1.262,-1.539 -1.621,-2.521c0.021,-0.095 0.044,-0.173 0.062,-0.256c0,0 0.387,-2.208 0.283,-2.954c-0.015,-0.105 -0.021,-0.132 -0.051,-0.212l-0.033,-0.089c-0.104,-0.243 -0.313,-0.502 -0.639,-0.488l-0.19,-0.006l-0.003,0c-0.362,0 -0.661,0.186 -0.736,0.461c-0.236,0.872 0.007,2.171 0.448,3.856l-0.114,0.275c-0.315,0.768 -0.711,1.542 -1.058,2.225l-0.048,0.09c-0.365,0.717 -0.7,1.328 -1,1.843l-0.313,0.167c-0.021,0.014 -0.556,0.294 -0.681,0.37c-1.064,0.634 -1.77,1.356 -1.887,1.929c-0.037,0.181 -0.009,0.414 0.18,0.525l0.302,0.15c0.13,0.064 0.272,0.097 0.41,0.097c0.757,0 1.637,-0.941 2.845,-3.053c1.4,-0.457 2.994,-0.836 4.39,-1.045c1.062,0.6 2.369,1.015 3.194,1.015c0.147,0 0.274,-0.013 0.377,-0.042c0.156,-0.04 0.29,-0.13 0.372,-0.256c0.158,-0.238 0.193,-0.569 0.148,-0.91c-0.01,-0.1 -0.093,-0.226 -0.18,-0.311Z\' style=\'fill:%23fff;fill-rule:nonzero;\'/></svg>',
  592. 'file_icon_system': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><g><path d=\'M1.247,6.495l3.263,0l0,-1.067l-0.881,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.052,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M7,6.588c1.082,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.743,-2.521 -1.825,-2.521c-1.082,0 -1.825,0.843 -1.825,2.521c0,1.677 0.743,2.567 1.825,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M9.598,6.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.283,0.479l0,0.82l0.927,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M2.825,12.588c1.082,0 1.824,-0.89 1.824,-2.567c0,-1.67 -0.742,-2.521 -1.824,-2.521c-1.083,0 -1.825,0.843 -1.825,2.521c0,1.677 0.742,2.567 1.825,2.567Zm0,-1.021c-0.31,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.262,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M5.423,12.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M11.175,12.588c1.083,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.742,-2.521 -1.825,-2.521c-1.082,0 -1.824,0.843 -1.824,2.521c0,1.677 0.742,2.567 1.824,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.31,0 0.572,0.201 0.572,1.5c0,1.299 -0.262,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></g></svg>',
  593. 'file_icon_text': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M14,0l-14,0l0,14l14,0l0,-14Z\' style=\'fill:%236A6A95;fill-rule:nonzero;\'/><rect x=\'6.5\' y=\'1.5\' width=\'6\' height=\'1\' style=\'fill:%23fff;\'/><rect x=\'1.5\' y=\'1.5\' width=\'3.5\' height=\'3.5\' style=\'fill:%23fff;\'/><rect x=\'1.5\' y=\'6.5\' width=\'11\' height=\'1\' style=\'fill:%23fff;\'/><rect x=\'6.5\' y=\'4\' width=\'6\' height=\'1\' style=\'fill:%23fff;\'/><rect x=\'1.5\' y=\'11.5\' width=\'8\' height=\'1\' style=\'fill:%23fff;\'/><rect x=\'1.5\' y=\'9\' width=\'11\' height=\'1\' style=\'fill:%23fff;\'/></svg>',
  594. 'file_icon_video': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><g id=\'Layer1\'><path d=\'M14,14l0,-14l-14,0l0,14l14,0Z\'/><path d=\'M9.5,3l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M3.5,3l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M6.5,3l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M12.5,3l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M9.5,13l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M3.5,13l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M6.5,13l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M12.5,13l0,-2l-2,0l0,2l2,0Z\' style=\'fill:%23fff;\'/><path d=\'M12.5,10l0,-6l-11,0l0,6l11,0Z\' style=\'fill:%23eda412;\'/></g></svg>',
  595. };
  596. // Programatically add File icon CSS rules
  597. function CSS_UI_Icon_Rules(el) {
  598. let class_name;
  599. let selector;
  600. let kinds = ['dir','dir_invisible','file','alias','code','font','audio','video','htm','image','pdf','text','markdown','app','invisible','ignore','archive','bin','database','ebook','graphics','ignored_image','office','system'];
  601. let rules = '';
  602. if ( el === 'body' ) {
  603. rules += '#bookmarks ul a { background-image:'+ SVG_UI_File_Icon('file_icon_file') +'; }';
  604. rules += '#bookmarks ul a[href^="file"] { background-image:'+ SVG_UI_File_Icon('file_icon_dir') +'; }';
  605. rules += '#bookmarks ul a[href^="http"] { background-image:'+ SVG_UI_File_Icon('file_icon_htm') +'; }';
  606. rules += 'body:not(.use_custom_icons) #dir_list tr.dir a.icon span { background-image:'+ SVG_UI_File_Icon('file_icon_dir_default') + '; background-size:auto 13px; }';
  607. rules += 'body:not(.use_custom_icons) #dir_list tr.file:not(.app) a.icon span { background-image:'+ SVG_UI_File_Icon('file_icon_file_default') + '; background-size:auto 13px; }';
  608. } else {
  609. rules += '#iframe_body:not(.use_custom_icons) #dir_list tr.dir td.name a::before { background:'+ SVG_UI_File_Icon('file_icon_dir_default') +'; }';
  610. rules += '#iframe_body:not(.use_custom_icons) #dir_list tr.file td.name a::before { background:'+ SVG_UI_File_Icon('file_icon_file_default') +'; }';
  611. }
  612. for ( let kind of kinds ) {
  613. class_name = kind;
  614. switch(kind) {
  615. case 'dir': class_name = 'dir:not(.app)'; break;
  616. case 'dir_invisible': class_name = 'dir.invisible'; break;
  617. }
  618. if ( el === 'body' ) {
  619. selector = el +'.use_custom_icons #dir_list tr.'+ class_name +':not(.media) a.icon span ';
  620. } else {
  621. selector = el +'.use_custom_icons #dir_list tr.'+ class_name +' td.name a::before ';
  622. }
  623. let background_image = '{ background-image: url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons['file_icon_'+kind] +'"); }' ;
  624. let rule = selector + background_image;
  625. rules += rule;
  626. }
  627. return rules;
  628. }
  629. //***** STYLES *****//
  630.  
  631. // DEFINE STYLES
  632. var $main_style_rules =
  633. '#content_pane.has_image #content_container { align-items: center; justify-content: center; }' +
  634. '.image_grid_item a { align-self: center; justify-self: center; }' +
  635. '#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' +
  636. '{ background-color: lightgray; }' +
  637. // light non-media ("light cyan")
  638. 'body.theme_light #dir_list tr.selected:not(.playing), body.theme_light #dir_list tr.selected.loaded:not(.playing), body.theme_light #dir_list tr.selected.loaded:hover'+
  639. '{ background-color: rgba(172,202,235,1.00) !important; }' +
  640. 'body.theme_light #dir_list tr.loaded:not(.selected), body.theme_light #dir_list tbody tr:not(.media):hover, body.theme_light.alternate_background #dir_list #tbody tr:not(.media):hover, body.theme_light #dir_list .hovered:not(.media), body.theme_light.alternate_background #dir_list #tbody tr.hovered:not(.media)'+
  641. '{ background-color: rgba(172,202,235,0.60); }' +
  642. // light media ("light aqua")
  643. 'body.theme_light #dir_list tr.media.playing { background-color: rgba(130,196,196,1); }' + // #82C4C4
  644. 'body.theme_light #dir_list tr.media.selected:not(.playing)'+
  645. '{ background-color: rgba(116,190,190,0.60) !important; }' +
  646. 'body.theme_light #dir_list tr.media.selected:not(.playing):hover, body.theme_light #dir_list tr.media:not(.selected):hover'+
  647. '{ background-color: rgba(116,190,190,0.40) !important; }' +
  648. // dark non-media ("dark cyan")
  649. 'body.theme_dark #dir_list tr.selected:not(.playing), body.theme_dark #dir_list tr.selected.loaded:not(.playing), body.theme_dark #dir_list tr.selected.loaded:hover'+
  650. '{ background-color: rgba(101,140,179,1.00) !important; }' + //#658CB3
  651. 'body.theme_dark #dir_list tr.loaded:not(.selected), body.theme_dark #dir_list tbody tr:not(.media):hover, body.theme_dark.alternate_background #dir_list #tbody tr:not(.media):hover, body.theme_dark #dir_list .hovered:not(.media), body.theme_dark.alternate_background #dir_list #tbody tr.hovered:not(.media)'+
  652. '{ background-color: rgba(101,140,179,0.66); }' +
  653. // dark media ("dark aqua")
  654. 'body.theme_dark #dir_list tr.media.playing { background-color: rgba(076,143,143,1.00) !important; }' +
  655. 'body.theme_dark #dir_list tr.media.selected:not(.playing)'+
  656. '{ background-color: rgba(076,143,143,0.70) !important; }' +
  657. 'body.theme_dark #dir_list tr.media.selected:not(.playing):hover, body.theme_dark #dir_list tr.media:not(.selected):hover'+
  658. '{ background-color: rgba(076,143,143,0.45) !important; }' +
  659. // text editor row ("purple")
  660. 'body.theme_light.has_text #text_editor_row, body.theme_light.edited #text_editor_row' +
  661. '{ background-color: rgba(160,160,230,1.00); }' + // #A0A0E6
  662. 'body.theme_dark.has_text #text_editor_row, body.theme_dark.edited #text_editor_row' +
  663. '{ background-color: rgba(100,100,160,1.00); }' + // #6464A0
  664. // other UI elements
  665. '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' +
  666. '{ background-color: #262626; }' +
  667. 'body.theme_dark #content_pane, body.theme_dark #content_pane.has_image, body.theme_dark #content_pane.has_grid #content_grid::after, body.theme_dark #content_grid > div, body.theme_dark #content_font, .split_btn::after' +
  668. '{ background-color: #333; }' +
  669. '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(.playing):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), body.theme_dark #font_info, body.theme_dark #glyph_viewer_info' +
  670. '{ background-color: #444; }' +
  671. 'body.theme_dark #sidebar, body.theme_dark #dir_list #tbody, 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' +
  672. '{ background-color: #555; }' +
  673. 'body.theme_dark #sidebar ul li:not(#preview_text_menu_item):hover, body.theme_dark li.has_submenu:hover, body.theme_dark #preview_text_menu_item span:hover, body.theme_dark #preview_text:hover, body.theme_dark #source_text:hover' +
  674. '{ background-color: #666; }' +
  675. 'body.theme_dark #sidebar .hovered, body.theme_dark #dir_list tbody tr: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^="/"]' +
  676. '{ background-color: #777; }' +
  677. 'body.theme_dark #grid_btn .menu li:hover' +
  678. '{ background-color: #888 !important; }' +
  679. 'body.theme_dark #grid_btn .menu, body.theme_dark #grid_btn .menu li' +
  680. '{ background-color: #AAA; }' +
  681. 'body.theme_light #sidebar_header thead tr, body.theme_light #sidebar_menus, #parents_dir_menu + ul li:hover, #bookmarks li:hover, body.theme_light #preview_text_menu_item span:hover, body.theme_light #preview_text:hover, body.theme_light #source_text:hover, body.theme_light #grid_btn .menu li:hover' +
  682. '{ background-color: #BBB; }' +
  683. '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), body.theme_dark #glyph_viewer' +
  684. '{ background-color: #CCC; }' +
  685. 'body.theme_light #dir_list tbody { background-color: #DFDFDF !important; }' +
  686. '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 #font_info, body.theme_light #glyph_viewer_info' +
  687. '{ background-color: #EEE; }' +
  688. '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, body.theme_light #glyph_viewer' +
  689. '{ background-color: #FFF; }' +
  690. 'body.is_chrome #audio_container { background-color: rgb(241, 243, 244); }' +
  691. '#warnings button:focus,#warnings button.focus { background-color: #0E4399; }' +
  692. '#bookmarks_menu div { background-image:'+ SVG_UI_Icon('menu') + '; }' +
  693. 'body.theme_light .bookmarks > a::before { background-image:'+ SVG_UI_Icon('bookmark') +'; }' +
  694. 'body.theme_dark .bookmarks > a::before { background-image:'+ SVG_UI_Icon('bookmark_dark') +'; }' +
  695. 'body.theme_light li.has_submenu { background-image:'+ SVG_UI_Icon('arrow') +'; }' +
  696. 'body.theme_dark li.has_submenu { background-image:'+ SVG_UI_Icon('arrow_dark') +'; }' +
  697. '#theme::before, body.sort_by_default #default span::before, body.sort_by_name #name span::before, body.sort_by_size #size span::before, body.sort_by_date #date span::before, body.sort_by_kind #kind span::before, body.sort_by_ext #ext span::before, body.alternate_background #alternate_background span::before, body.show_numbers #show_numbers span::before, body.autoload_media #autoload_media span::before, body.split_view #split_view_menu_item, body.split_view #split_view span::before, body:not(.enable_text_editing) #enable_text_editing::before, body.source_text:not(.preview_text) #source_text::before, body.preview_text:not(.source_text) #preview_text::before, body.sort_by_default #sort_by_default span::before, body.sort_by_name #sort_by_name span::before, body.sort_by_size #sort_by_size span::before, body.sort_by_date #sort_by_date span::before, body.sort_by_kind #sort_by_kind span::before, body.sort_by_ext #sort_by_ext span::before'+
  698. '{ background-image:'+ SVG_UI_Icon('check_mark') +'; }' +
  699. '#parent_dir_menu a, body.sort_by_default #sort_by_default span::after, body.sort_by_name #sort_by_name span::after, body.sort_by_size #sort_by_size span::after, body.sort_by_date #sort_by_date span::after, body.sort_by_kind #sort_by_kind span::after, body.sort_by_ext #sort_by_ext span::after'+
  700. '{ background-image:'+ SVG_UI_Icon('chevron_up') +'; }' +
  701. // '{ background-image:'+ SVG_UI_Icon('chevron_up_dark') +'; }' +
  702. '#prev_btn, #next_btn { background-image:'+ SVG_UI_Icon('chevron_left') + '; }' +
  703. 'body.is_error #content_pane, #warnings h3::before { background-image:'+ SVG_UI_Icon('error') +'; }' +
  704. 'body.has_fonts #grid_btn, body.has_images #grid_btn, #grid_btn .menu'+
  705. '{ background-image:'+ SVG_UI_Icon('grid') +'; }' +
  706. 'body #content_pane.has_ignored:not(.has_grid) { background-image:'+ SVG_UI_Icon('ignored') +'; }' +
  707. 'body.theme_dark #content_pane.has_ignored:not(.has_grid){ background-image:'+SVG_UI_Icon('ignored_dark') +'; }' +
  708. '#decrease { background-image:'+ SVG_UI_Icon('minus') +'; }' +
  709. '#close_audio { background-image:'+ SVG_UI_Icon('multiply') +'; }' +
  710. 'body.has_audio #content_pane:not(.has_image):not(.has_video):not(.has_ignored):not(.has_grid)'+
  711. '{ background-image:'+ SVG_UI_Icon('music') +'; }' +
  712. '#increase { background-image:'+ SVG_UI_Icon('plus') +'; }' +
  713. '#prev_track, #next_track { background-image:'+ SVG_UI_Icon('prev_next_track') +'; }' +
  714. '#toggle_sidebar, #toggle_info { background-image:'+ SVG_UI_Icon('toggle') +'; }' +
  715. CSS_UI_Icon_Rules('body') + // FILE & DIR ICONS
  716. '#parent_dir_menu a, #bookmarks_menu div, #bookmarks li a::before, #bookmarks li.toggle_UI_pref span::before, #theme::before, #enable_text_editing::before, #sort_menu span::before, #preview_text_menu_item span::before, #sorting div span::before, #sorting div span::after, #toggle_sidebar, #toggle_info, #content_pane, #decrease, #increase, #close_audio, #prev_track, #next_track, #prev_btn, #next_btn, #glyph_viewer' +
  717. '{ background-position: center; }' +
  718. '.toggle_UI_pref, .bookmarks > a { background-position: 5px center; }' +
  719. '#dir_list tbody a { background-position: 6px 4px; }' +
  720. '#dir_list a.icon span { background-position: 6px 0; }' +
  721. '#bookmarks ul a { background-position: 8px center; }' +
  722. '#bookmarks > li { background-position: right 6px center; }' +
  723. '#grid_btn,#grid_btn .menu { background-position: right 5px top 6px; }' +
  724. '#bookmarks li a::before, .toggle_UI_pref, #toggle_sidebar, #grid_btn, #grid_btn .menu, #toggle_info, #main_content a, a, div, li, span, span::before, span::after, #dir_list a.icon span, #content_pane, #glyph_viewer, #warnings h3::before'+
  725. '{ background-repeat: no-repeat; }' +
  726. '#glyph_viewer { background-size: contain; }' +
  727. '.toggle_UI_pref, th .sorting, #decrease, #increase { background-size: 10px; }' +
  728. '.bookmarks > a, li.has_submenu, #parent_dir_menu a { background-size: 12px; }' +
  729. '#dir_list tbody a, #dir_list a.icon span, #bookmarks ul a, #close_audio, #grid_btn, #grid_btn .menu'+
  730. '{ background-size: 14px; }' +
  731. '#toggle_sidebar, #toggle_info { background-size: 18px; }' +
  732. '#warnings h3::before { background-size: 24px; }' +
  733. '#content_pane { background-size: 50%; }' +
  734. 'body.has_audio #content_pane:not(.has_image):not(.has_video):not(.has_ignored):not(.has_grid)'+
  735. '{ background-size: 75%; }' +
  736. ':root, html, body, #sidebar_wrapper, #sidebar_header, #dir_list, #main_content, #content_pane, #content_pdf, #content_iframe' +
  737. '{ border: 0 !important; }' +
  738. 'body.theme_dark ul { border: solid 1px #222; }' +
  739. 'button { border: solid 1px #333; }' +
  740. 'body.theme_light #sidebar ul { border: solid 1px gray; }' +
  741. 'body.theme_dark #sidebar_header .menu, body.theme_dark #text_editor_row, body.theme_dark:not(.is_error) #dir_list #tbody, body.theme_dark #tfoot' +
  742. '{ border-top: solid 1px #111; }' +
  743. 'body.theme_light #dir_list tbody, #grid_btn .menu, body.theme_light #bookmarks, body.theme_light #text_editor_row, #tfoot, .font_grid_item:not(:first-of-type), .image_grid_item + .font_grid_item, #font_info th, #font_info td' +
  744. '{ border-top: solid 1px grey; }' +
  745. '#sort_by, tr.sorted, tr:not(.invisible) ~ tr.invisible + tr:not(.invisible)' + // , tr.dir ~ tr:not(.dir,.invisible,.ignore)
  746. '{ border-top: solid 1px #999; }' +
  747. 'body.theme_dark #grid_btn ul.menu { border-top: solid 1px #EEE; }' +
  748. '#parents_dir_menu + ul, #bookmarks_menu + ul, #grid_btn ul.menu'+
  749. '{ border-right: 0 !important; }' +
  750. 'body.theme_dark #parents_dir_menu, body.theme_dark #sidebar, body.theme_dark #warnings' +
  751. '{ border-right: solid 1px #111; }' +
  752. '#sidebar, body.theme_light #parents_dir_menu, body.theme_light #grid_btn .menu li, .image_grid_item, div.glyph_container, #font_info td:first-of-type' +
  753. '{ border-right: solid 1px grey; }' +
  754. 'body.theme_dark #grid_btn .menu li { border-right: solid 1px #EEE; }' +
  755. '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 #warnings' +
  756. '{ border-bottom: solid 1px #111; }' +
  757. 'body.theme_light #sidebar_header thead tr, body.theme_light #bookmarks, body.theme_light #sidebar_menus, body.theme_light #grid_btn .menu li#show_image_grid, #grid_btn .menu, .specimen, .font_grid_item:last-of-type, .image_grid_item, div.glyph_container, #glyph_viewer_info' +
  758. '{ border-bottom: solid 1px grey; }' +
  759. 'body.theme_light #content_title, body.theme_light #content_pane.has_audio #content_header' +
  760. '{ border-bottom: solid 1px #888; }' +
  761. '#bookmarks_menu + ul > li.has_submenu.ruled, .rule { border-bottom: solid 1px #999; }' +
  762. 'body.theme_dark #content_pane.has_font #content_font hr, .hamburger h4'+
  763. '{ border-bottom: solid 1px #CCC; }' +
  764. 'body.theme_dark #grid_btn ul.menu, body.theme_dark #grid_btn .menu li#show_image_grid'+
  765. '{ border-bottom: solid 1px #EEE; }' +
  766. '#parents_dir_menu + ul, #bookmarks_menu + ul { border-left:0; }' +
  767. 'body.theme_dark #parents_dir_menu, body.theme_dark #grid_btn ul.menu, body.theme_dark #warnings' +
  768. '{ border-left: solid 1px #111; }' +
  769. 'body.theme_light #parents_dir_menu, #grid_btn .menu { border-left: solid 1px grey; }' +
  770. 'body.theme_dark #grid_btn ul.menu { border-left: solid 1px #EEE; }' +
  771. 'table { border-collapse: collapse; }' +
  772. 'html, body, :root { border-radius: 0; }' +
  773. 'button { border-radius: 3px; }' +
  774. '#warnings { border-radius: 0 0 3px 3px; }' +
  775. 'body.theme_light #sidebar ul { box-shadow: 0px 2px 3px -2px #888; }' +
  776. 'body.theme_dark #sidebar ul { box-shadow: 0px 2px 3px -2px #111; }' +
  777. '#warnings { box-shadow: 0px 2px 12px 0 #111; }' +
  778. 'body.theme_dark #grid_btn ul.menu { box-shadow: none; }' +
  779. 'html, body, :root, #sidebar, #grid_btn .menu li, #content_container, #content_source, #content_preview' +
  780. '{ box-sizing: border-box; }' +
  781. '#checkbox_div label, #grid_btn .menu li, #content_grid .font_grid_item'+
  782. '{ clear: both; }' +
  783. '#dir_list tbody .name { clear: right; }' +
  784. '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, body.theme_dark #grid_btn .menu li' +
  785. '{ color: #111 }' +
  786. 'body.theme_light #sidebar #tfoot tr { color: #444 }' +
  787. 'body.theme_light #dir_list tr.ignore a, body.theme_light #dir_list tr.ignore.app a, .glyph_info' +
  788. '{ color: #777 }' +
  789. '#sort_menu .disabled span, #dir_list th.disabled { color: #999 }' +
  790. 'body.theme_dark #dir_list tr.ignore td, body.theme_dark #dir_list tr.file.ignore a, body.theme_dark #sidebar #tfoot td' +
  791. '{ color: #BBB }' +
  792. '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' +
  793. '{ color: #EEE }' +
  794. '#warnings h3::before, #bookmarks li > a::before, #bookmarks li > span::before, #preview_text_menu_item span::before, #sorting div span::before, #sorting div 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, #content_pane.has_grid #content_grid::after' +
  795. '{ content: "" }' +
  796. '#content_pane.has_audio #content_audio_title td::before, body #content_pane.has_video #title::before' +
  797. '{ content: "Playing: " }' + // .audio, .video
  798. '#content_pane.has_dir:not(.has_grid) #title::before { content: "Index of: " }' + // .dir
  799. '#content_pane.has_grid:not(.has_ignored) #title::before{ content: "Fonts and Images from: " }' + // font and image grid
  800. 'body:not(.has_images) #content_pane.has_grid:not(.has_ignored) #title::before, #content_pane.has_grid #title.font_grid::before' +
  801. '{ content: "Fonts from: " }' + // font grid
  802. 'body:not(.has_fonts) #content_pane.has_grid:not(.has_ignored) #title::before, #content_pane.has_grid #title.image_grid::before' +
  803. '{ content: "Images from: " }' + // image grid
  804. '#content_pane.has_ignored:not(.has_grid) #title::before{ content: "Ignored content: " }' + // .ignored
  805. 'body.edited.has_text #title::after, body.iframe_edited:not(.has_text) #content_pane.has_iframe #title::after' +
  806. '{ content: " (edited)" }' + // .ignored
  807. 'body#top.edited #text_editor_row a:after { content: " (edited)" }' +
  808. 'body#top.edited #warnings.unloading h3::before { content: "Text Editor: " }' +
  809. '#title::after { content: attr(data-after) }' +
  810. '#glyph_viewer_info div::before { content: "Glyph: " }' +
  811. 'body.show_numbers #tbody { counter-reset: row; }' +
  812. 'body.show_numbers #tbody tr td.name a::before { counter-increment: row; content:counter(row);}' +
  813. '#sidebar_title th, #sort_menu .disabled span, #dir_list .disabled'+
  814. '{ cursor: default; }' +
  815. '#sidebar_menus td:hover, #parents_dir_menu div, #bookmarks_menu div, .menu label, #grid_btn, #sorting .toggle_UI_pref, #toggle_info, .split_btn span' +
  816. '{ cursor: pointer; }' +
  817. '#handle { cursor: col-resize; }' +
  818. '#content_pane:not(.has_zoom_image) #content_image { cursor: zoom-in; }' +
  819. '#content_pane.has_zoom_image content_image { cursor: zoom-out; }' +
  820. '#sidebar_menus ul, #show_details span, #grid_btn .menu, body:not(.has_media) #sorting .name input, #bookmarks li#theme span span, #preview_text::before, #source_text::before, #sidebar_buttons td.up::before, body:not(.show_details) #sorting .details.sorting, #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, #glyph_viewer, #font_info tbody' +
  821. '{ display: none; }' +
  822. 'body.is_error #sidebar_buttons, body.is_error #content_pane.has_iframe #content_iframe.has_content' +
  823. '{ display: none !important; }' +
  824. '#sidebar li, #parent_dir_menu, #parent_dir_menu a, #parents_dir_menu + ul li a, #bookmarks_menu + ul li a, #bookmarks_menu + ul li > span, #bookmarks_menu + ul > li:hover > ul, #bookmarks_menu + ul > li > ul:hover, .menu label, body.show_details #show_details span:last-of-type, body:not(.show_details) #show_details span:first-of-type, #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' +
  825. '{ display: block; }' +
  826. 'body.has_images.has_fonts #grid_btn:hover ul.menu, #font_info:hover tbody' +
  827. '{ display:block !important; }' +
  828. '#parents_dir_menu div, #bookmarks li > a::before, #bookmarks li > span::before, #bookmarks li#theme > span::before, body.theme_dark #bookmarks #theme_dark, body:not(.theme_dark) #bookmarks #theme_light, body.preview_text #preview_text::before, body.source_text #source_text::before, #sorting div span::before, #enable_text_editing::before, #sorting div span::after, body.show_details #dir_list td.details:not(.name):not(.ext), body.show_numbers #tbody tr td.name a::before, #title, #checkbox_div, #audio_container, #prev_track, #next_track, #close_audio, #warnings h3::before, 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, #glyph_viewer_info div' +
  829. '{ display: inline-block; }' +
  830. '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, #font_info tbody tr' +
  831. '{ display: flex; }' +
  832. '#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' +
  833. '{ display: inline-flex; }' +
  834. 'body.is_gecko #dir_list tr td.name span { display: flow-root; }' +
  835. 'body.has_text #text_editor_row, body.show_details #text_editor_row, body.has_hidden_text #text_editor_row, #content_pane.has_audio #content_audio_title, #content_pane.has_audio #content_audio' +
  836. '{ display: table-row !important; }' +
  837. '#bookmarks_menu div, #content_pane, #content_pane.has_audio #content_audio td' +
  838. '{ display: table-cell; }' +
  839. '#sorting > div { display: grid; grid-gap:0; grid-template-columns: 1fr 1fr 1fr 1fr; }' +
  840. '#sort_by_name { grid-column: 1 / span 2; }' +
  841. '#sort_by_default { grid-column: 3 / span 2; }' +
  842. // display grid: dir_list rows and cells
  843. '#dir_list #tbody tr:not(.invisible), body.show_invisibles #tbody tr.invisible, body.show_details #dir_list tr td.details' +
  844. '{ display: grid; grid-gap:0; grid-template-columns: minmax(auto,6rem) 1fr minmax(auto,6rem); }' +
  845. 'td.name { grid-column: 1 / span 3; }' +
  846. 'td.size { grid-column: 1; grid-row: 2; }' +
  847. 'td.date { grid-column: 2; grid-row: 2; }' +
  848. 'td.kind { grid-column: 3; grid-row: 2; }' +
  849. // display grid: content pane grids
  850. '#content_pane.has_grid #content_grid { 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); }' +
  851. '#content_pane.has_grid #content_grid .image_grid_item { grid-column: auto; display:flex; align-items: center; }' +
  852. '#content_pane.has_grid #content_grid .font_grid_item { grid-column: 1 / -1; }' +
  853. // glyphs container
  854. '#content_font div#glyphs_container { display:grid; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(120px,auto)); }' +
  855. 'body.theme_dark #bookmarks li > span::before, body.theme_dark #grid_btn, body.theme_dark #parent_dir_menu, body.theme_dark #bookmarks_menu, body.theme_dark #toggle_sidebar, body.theme_dark #toggle_info, body.theme_dark .glyph_container canvas, body.theme_dark #glyph_viewer:not(#glyph_viewer_info), body.theme_dark #glyph_viewer #glyph_viewer_info, body.theme_dark #sorting span::before, body.theme_dark #sorting span::after ' +
  856. '{ filter:invert(1); }' +
  857. '.menu input, #dir_list tr td.name a::before, #title_buttons_left button, #checkbox_div label, #save_svg, #warnings h3::before, #warnings h3::before' +
  858. '{ float: left; }' +
  859. 'html, body, :root { font-family:'+ $settings.UI_font +'; }' +
  860. 'body, :root, #content_text { font-size: '+ $settings.UI_font_size +'; }' +
  861. '#sidebar, #sidebar_header, #dir_list, #tfoot, #content_header, #content_header table, #warnings, #font_info td, #font_info th, #glyph_viewer_info' +
  862. '{ font-size: 0.875rem; }' +
  863. '#content_grid, .font_grid_item p, #error_message h1, #error_message h2' +
  864. '{ font-size: 1rem; }' +
  865. '#content_grid .font_grid_item h2 { font-size: '+ $settings.grid_font_size * 4 +'em; }' +
  866. '#content_font { font-size: '+ $settings.grid_font_size +'em; }' +
  867. '.lorem.first:first-line { font-size: '+ $settings.grid_font_size * 1.33 +'em; }' +
  868. 'td.name, td.name a::before, td.size, td.date { font-variant-numeric: tabular-nums; }'+
  869. '.lorem.first:first-line { font-variant: small-caps }' +
  870. '#sidebar_title th, h1, h2, h3, h4, h5, h6, #font_info th'+
  871. '{ font-weight: normal }' +
  872. '#dir_list .selected a, body.has_text #text_editor_row, body.edited:not(.has_hidden_text) #text_editor_row, #font_info td:first-of-type' +
  873. '{ font-weight: bold }' +
  874. 'html, body, :root, #parent_dir_menu a, #dir_list, #toggle_info, #main_content, #content_pane, #content_pdf, #content_text, #content_iframe' +
  875. '{ height: 100%; }' +
  876. '#sidebar_header, #parents_dir_menu, #parents_dir_menu div, #content_pane:not(.has_zoom_image) #content_image, #content_grid div img' +
  877. '{ height: auto; }' +
  878. '.menu input { height: 0; }' +
  879. '#sorting div span::after { height: 8px; }' +
  880. '#bookmarks .toggle_UI_pref span::before, #theme::before, #sort_menu span::before, #preview_text_menu_item span::before, #sorting div span::before, #enable_text_editing::before'+
  881. '{ height: 9px; }' +
  882. '#bookmarks li.bookmarks a::before { height: 12px; }' +
  883. '#toggle_sidebar, #toggle_info { height: 14px; }' +
  884. '#grid_btn, button { height: 18px; }' +
  885. '#warnings h3::before { height: 24px; }' +
  886. '#audio { height: 32px; }' +
  887. '#main_content { height: '+ window.innerHeight +'px; }' +
  888. '#content_pane:not(.has_zoom_image) #content_image { max-height: calc(100% - 10em); }' +
  889. '#font_info tbody { max-height: '+ (window.innerHeight * 0.75) +'px; }' +
  890. '#content_pane.has_zoom_image #content_image { max-height: none; }' +
  891. '#content_grid div img { max-height: '+ ($settings.grid_image_size) +'px; }' +
  892. '#title { min-height: 18px; }' +
  893. '#sidebar { min-height: 100%; }' +
  894. '#parents_dir_menu div, #content_header td button, #content_font'+
  895. '{ hyphens: none; }' +
  896. 'html, body, :root, .hamburger h3, .lorem { hyphens: auto; }' +
  897. '.lorem.first:first-line, .font_grid_item p, #font_info th'+
  898. '{ letter-spacing: 0.1em }' +
  899. '#sidebar_title th { letter-spacing: 0.5em }' +
  900. '.image_grid_item a, button { line-height: 0; }' +
  901. '.split_btn span, .font_grid_item p { line-height: 1; }' +
  902. '#dir_list tr a, #title { line-height: 1.4; }' +
  903. '#warnings h3 { line-height: 2; }' +
  904. '#sidebar ul, #grid_btn .menu li { list-style-type: none; }' +
  905. 'html, body, :root, #sidebar ul, #parent_dir_menu, #parent_dir_menu, #parents_dir_menu, #parents_dir_menu + ul li a, #bookmarks_menu + ul li a, #bookmarks_menu + ul li > span, #bookmarks_menu, #grid_btn, #dir_list tbody a, #content_font .hamburger *, #content_font canvas, #content_grid p, #content_grid h2, #content_text, #warnings h3' +
  906. '{ margin: 0; }' +
  907. '.image_grid_item + .font_grid_item { margin-top: -1px; }' +
  908. '#checkbox_div label input, #show_details, body.is_error #tbody h1'+
  909. '{ margin-top: 0; }' +
  910. 'body.has_audio #dir_list input { margin-top: 1px; }' +
  911. '#content_image, #content_video, .image_grid_item a { margin-right: auto; }' +
  912. '#increase { margin-right: -4px; }' +
  913. 'th input, tr.media input, #scale { margin-right: 8px; }' +
  914. '#show_details { margin-right: 0.5em; }' +
  915. '#warning_ignore_btn { margin-right: 2em; }' +
  916. '#checkbox_div { margin-right: calc(-6em - 4px); }' +
  917. '#bookmarks li a::before { margin-bottom: -2px; }' +
  918. '#show_details { margin-bottom: 0; }' +
  919. 'body.has_audio #dir_list input { margin-bottom: 1px; }' +
  920. '#content_grid.has_image_grid { margin-bottom: 1rem; }' +
  921. '#content_image, #content_video, .image_grid_item a { margin-left: auto; }' +
  922. '#decrease { margin-left: -4px; }' +
  923. 'th input { margin-left: 2px; }' +
  924. 'tr.media input { margin-left: 7px; }' +
  925. '#prev_next_btns { margin-left: 8px; }' +
  926. '#show_details, #warnings button { margin-left: 0.5em; }' +
  927. '#warnings { margin-left: -12em; }' +
  928. '#dir_list tbody tr { margin-inline-start: 0; }' +
  929. '#sidebar ul { -webkit-margin-before: 0em !important; -webkit-margin-after: 0em !important; -webkit-padding-start:0em; }' +
  930. '#content_image { object-fit:contain; }' +
  931. '#open_font { opacity: 0.0; }' +
  932. '#content_pane.has_ignored::before, #close_audio::after { opacity: 0.3; }' +
  933. '#close_audio:hover::after { opacity: 0.6; }' +
  934. '#sidebar_menus td:last-of-type:hover > div, #bookmarks_menu_container #bookmarks_menu, #parent_dir_menu, #content_pane:hover #prev_btn, #content_pane:hover #next_btn, #toggle_sidebar, #grid_btn, #toggle_info, body.use_custom_icons #dir_list tr.file.ignore a.icon::before, .split_btn span' +
  935. '{ opacity: 0.7; }' +
  936. '#content_grid div img { opacity: 0.8; }' +
  937. '#grid_btn:hover, #parent_dir_menu:hover, #bookmarks_menu_container:hover #bookmarks_menu, #content_pane #prev_btn:hover, #content_pane #next_btn:hover, #toggle_sidebar:hover, #grid_btn:hover, #toggle_info:hover, .split_btn span:hover' +
  938. '{ opacity: 1.0; }' +
  939. '#grid_btn, #dir_list tbody, #dir_list tbody a, #audio:focus, #content_grid .font_grid_item, #content_font, #warnings button:focus, #content_video' +
  940. '{ outline: none; }' +
  941. 'html, body, :root, #parents_dir_menu div, #bookmarks_menu, #dir_list, #dir_list tbody a, #main_content, #close_audio, .hamburger h1, .hamburger h2, .hamburger h4' +
  942. '{ overflow: hidden; }' +
  943. '#dir_list tbody, #next_track, #prev_track, #content_container, #content_font, #content_grid, #font_info tbody' +
  944. '{ overflow: auto; }' +
  945. '#sidebar, #content_font { overflow-wrap: break-word; }' +
  946. 'html, body, :root, #main_content > thead th, #sidebar_wrapper, #sidebar ul, #sidebar_menus > td, #parent_dir_menu, #parent_dir_menu, #parent_dir_menu a, #parents_dir_menu, #bookmarks_menu, #grid_btn, #dir_list .details a, #tfoot, #title_buttons_left button, #content_pane, #reload_btn, #close_btn, #audio_container > div, #content_pdf, #content_iframe, #content_text, audio::-webkit-media-controls-panel' +
  947. '{ padding: 0; }' +
  948. 'div#glyphs_container, div.glyph_container, #glyph_viewer, #glyph_viewer_info div'+
  949. '{ padding: 0 !important; }' +
  950. '#show_details { padding-top: 0; }' +
  951. '#sidebar_title th, #parents_dir_menu div, #parents_dir_menu + ul li a, #grid_btn .menu li, #dir_list tbody a, #stats, #title_buttons_left, #title_buttons_right, #title, body #content_audio_title td, #content_audio td, #content_font div#glyph_viewer_info, #font_info td' +
  952. '{ padding-top: 4px; }' +
  953. '#sidebar_header tbody tr:last-of-type td, #bookmarks_menu + ul li a, #bookmarks_menu + ul li > span, #bookmarks_menu + ul li div span, .menu label, #sorting .toggle_UI_pref, #sidebar_buttons td, #text_editor_row th, .image_grid_item, .hamburger h4' +
  954. '{ padding-top: 6px; }' +
  955. '#content_grid .font_grid_item, .font_grid_item p, #content_font div.lorem' +
  956. '{ padding-top: 0.5rem; }' +
  957. '#error_message, #warnings { padding-top: 1rem; }' +
  958. 'body:not(.has_text) #content_pane.has_image:not(.has_grid) #content_container, #content_font div.specimen' +
  959. '{ padding-top: 2rem; }' +
  960. '#sidebar_header tbody tr:last-of-type td { padding-right: 0; }' +
  961. 'div.glyph_container div.glyph_info { padding-right: 2px; }' +
  962. '#sidebar_title th, #scale { padding-right: 4px; }' +
  963. '#parents_dir_menu div, #grid_btn .menu li, #stats, #title_buttons_left, #title_buttons_right, #content_audio td, #audio_container, #content_font div#glyph_viewer_info, #font_info td, .image_grid_item' +
  964. '{ padding-right: 6px; }' +
  965. '#bookmarks_menu + ul li a, #bookmarks_menu + ul li > span, #bookmarks_menu + ul li div span, .menu label, body.show_numbers #tbody tr td.name a::before, #dir_list tbody a' +
  966. '{ padding-right: 8px; }' +
  967. '#parents_dir_menu + ul li a, #dir_list td.details, #title, #content_audio_title td'+
  968. '{ padding-right: 12px; }' +
  969. '#grid_btn .menu { padding-right: 24px; }' +
  970. '#error_message, #warnings { padding-right: 1rem; }' +
  971. '.font_grid_item p, .font_grid_item a { padding-right: 2rem; }' +
  972. 'body:not(.has_text) #content_pane.has_image:not(.has_grid) #content_container, #content_font > div' +
  973. '{ padding-right: 2.5rem; }' +
  974. '#show_details { padding-bottom: 0; }' +
  975. 'body.is_dirs_on_top #dir_list tbody tr.sorted:not(:last-of-type), div.glyph_container div.glyph_info' +
  976. '{ padding-bottom: 2px; }' +
  977. '#sorting, #title, body #content_audio_title td, #title_buttons_left, #title_buttons_right' +
  978. '{ padding-bottom: 3px;}' +
  979. '#sidebar_title th, #parents_dir_menu div, #parents_dir_menu + ul li a, #grid_btn .menu li, #dir_list tbody a, #sidebar .details, #content_font div#glyph_viewer_info, #font_info td' +
  980. '{ padding-bottom: 4px; }' +
  981. '#sidebar_header tbody tr:last-of-type td, #parents_dir_menu + ul li:last-of-type a, #bookmarks_menu + ul li a, #bookmarks_menu + ul li > span, #bookmarks_menu + ul li div span, .menu label, body:not(.show_details) #sorting, #text_editor_row th, #dir_list tfoot td, #content_audio td, .image_grid_item' +
  982. '{ padding-bottom: 6px; }' +
  983. '.hamburger h4 { padding-bottom: 9px; }' +
  984. '.specimen, .font_grid_item p, .font_grid_item a { padding-bottom: 0.5rem; }' +
  985. '#error_message, #warnings { padding-bottom: 1rem; }' +
  986. 'body:not(.has_text) #content_pane.has_image:not(.has_grid) #content_container, #content_font > div.specimen, #content_font .lorem:last-of-type' +
  987. '{ padding-bottom: 2rem; }' +
  988. '#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' +
  989. '{ padding-left: 0px; }' +
  990. 'div.glyph_container div.glyph_info { padding-left: 2px; }' +
  991. '#sidebar_title th, #bookmarks ul li a, #dir_list .audio a, #dir_list .video a, #checkbox_div, #scale' +
  992. '{ padding-left: 4px; }' +
  993. '#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, #grid_btn .menu li, #title_buttons_left, #title_buttons_right, #content_audio td, #audio_container, #content_font div#glyph_viewer_info, #font_info td, .image_grid_item' +
  994. '{ padding-left: 6px; }' +
  995. '#parents_dir_menu + ul a, #title, #content_audio_title td'+
  996. '{ padding-left: 12px; }' +
  997. '#text_editor_row a { padding-left: 18px; }' +
  998. '.menu label, body.has_hidden_sidebar #title_buttons_left'+
  999. '{ padding-left: 24px; }' +
  1000. '#dir_list #tbody tr:not(.media) a span { padding-left: 27px; -webkit-padding-start: 27px; }' +
  1001. '#dir_list #tbody td.details.size, tr.media a.icon { padding-left: 30px; }' +
  1002. '#error_message, #warnings { padding-left: 1rem; }' +
  1003. '.font_grid_item p, .font_grid_item a { padding-left: 2rem; }' +
  1004. 'body:not(.has_text) #content_pane.has_image:not(.has_grid) #content_container, #content_font > div' +
  1005. '{ padding-left: 2.5rem; }' +
  1006. '#dir_list td.name a { -webkit-padding-start: 0; }' +
  1007. '#dir_list .icon + td.name a { -webkit-padding-start: 1em; }' +
  1008. '#sidebar_wrapper, #sidebar_header, #sidebar_buttons, #sidebar_menus td:first-of-type, #grid_btn, #dir_list, 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, #bookmarks_menu + ul > li.has_submenu, #content_pane, #content_header, #content_grid, #content_text, .glyph_container, #content_pdf, #content_iframe, #close_audio, #content_grid div img, #content_text, .split_btn' +
  1009. '{ position: relative; }' +
  1010. '#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, .glyph_info' +
  1011. '{ position: absolute; }' +
  1012. '#tfoot, #glyph_viewer, #font_info { position: fixed; }' +
  1013. '#parent_dir_menu, #overlay, #glyph_viewer { top: 0; right: 0; bottom: 0; left: 0; }' +
  1014. '#dir_list, #handle, #content_pane.has_grid #content_grid::after, .split_btn::after'+
  1015. '{ top: 0; bottom: 0; }' +
  1016. '#sidebar ul, #tbody, .glyph_info, #tfoot { left: 0; right: 0; }' +
  1017. '#tbody, #toggle_info, #warnings { top: 0; }' +
  1018. '#bookmarks_menu + ul > li > ul, #grid_btn .menu { top: -1px !important; }' +
  1019. 'body.has_hidden_sidebar #sidebar_wrapper { top: 2px; }' +
  1020. '#toggle_sidebar { top: 4px; }' +
  1021. '#grid_btn .menu, #content_pane.has_grid #content_grid::after, #toggle_info'+
  1022. '{ right: 0; }' +
  1023. '#handle { right: -4px; }' +
  1024. '#toggle_sidebar { right: 4px; }' +
  1025. '#dir_list tbody, #tfoot, #content_container, .glyph_info, #font_info'+
  1026. '{ bottom: 0; }' +
  1027. 'body.has_hidden_sidebar #toggle_sidebar { left: 0; }' +
  1028. '#grid_btn ul.menu { left: unset; }' +
  1029. 'body.has_hidden_sidebar #sidebar_wrapper { left: 3px; }' +
  1030. 'body.show_numbers #tbody tr td.name a::before { left: 6px; }' +
  1031. '#warnings, .split_btn::after { left: 50%; }' +
  1032. '#bookmarks_menu + ul > li > ul { left: 100%; }' +
  1033. '#sidebar ul, #dir_list tbody, #sort_by_name, #sort_by_ext, #text_editor_row, #dir_list tbody .name, #dir_list tr.details, #title_buttons_left, #warnings' +
  1034. '{ text-align: left; }' +
  1035. '#parent_dir_menu a, #parents_dir_menu, #parents_dir_menu div, #bookmarks_menu div, #content_header, #content_title, #content_audio_title, #content_audio td, .glyph_container, #glyph_viewer_info' +
  1036. '{ text-align: center; }' +
  1037. '#grid_btn .menu li, body.show_numbers #tbody tr td.name a::before, #sort_by_default, #sort_by_kind, #dir_list .size, #dir_list .date, #dir_list .kind, #title_buttons_right, #warnings div, .playlist_time, #font_info td:first-of-type' +
  1038. '{ text-align: right; }' +
  1039. 'a, a:hover { text-decoration: none !important; }' +
  1040. '#dir_list, #content_pane { transform: scale(1); }' + // needed to establish #dir_list as containing block for thead and tfoot position fixed.
  1041. 'body.has_hidden_sidebar #toggle_sidebar, #sorting div.up span::after, #prev_track, #next_btn, #toggle_info' +
  1042. '{ transform:rotate(180deg) !important; }' +
  1043. '#sidebar_header, #content_pane:not(.has_zoom_image) #content_image' +
  1044. '{ user-select:none; -webkit-user-select:none; }' +
  1045. '#content_pane, #title_buttons_left, #title_buttons_right, #title'+
  1046. '{ vertical-align:top; }' +
  1047. '#sidebar_header tbody tr:last-of-type td, #parents_dir_menu div, #parents_dir_menu + ul li a, #bookmarks_menu + ul li a, #bookmarks_menu + ul li > span, #dir_list tbody a, .specimen, .lorem' +
  1048. '{ white-space: normal; }' +
  1049. '#sorting span, #tbody td:not(.name), #grid_btn .menu li, .hamburger h1, .hamburger h2, .hamburger h4' +
  1050. '{ white-space: pre; }' +
  1051. 'html, body, :root, table, #parent_dir_menu a, #tbody, #tbody tr, #bookmarks_menu + ul > li > ul, #grid_btn .menu li, #content_container, #content_header, #content_grid, #content_text, #content_font svg, #content_pdf, #content_iframe' +
  1052. '{ width: 100%; }' +
  1053. '#sidebar_menus td:nth-of-type(2), #dir_list .date, #title, #content_pane:not(.has_zoom_image) #content_image, #content_grid div img' +
  1054. '{ width: auto; }' +
  1055. '.split_btn span { width: 2em; }' +
  1056. '#content_pane.has_grid #content_grid::after, .split_btn::after' +
  1057. '{ width: 1px; }' +
  1058. '#handle { width: 8px; }' +
  1059. '#sorting div span::before, #sorting div span::after, #toggle_sidebar, #toggle_info' +
  1060. '{ width: 18px; }' +
  1061. '#sidebar_menus td:nth-of-type(odd), #bookmarks li a::before, #bookmarks li > span::before, #sort_menu span::before, #preview_text_menu_item span::before, #grid_btn'+
  1062. '{ width: 24px; }' +
  1063. '#warnings h3::before { width: 32px; }' +
  1064. '#prev_track, #next_track, #close_audio { width: 2rem; }' +
  1065. '#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' +
  1066. '{ width: 4rem; }' +
  1067. '#bookmarks_menu div, #checkbox_div { width: 6em; }' +
  1068. '#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' +
  1069. '{ width: 9.5em; }' +
  1070. '#warnings { width: 26em; }' +
  1071. 'body.has_hidden_sidebar #sidebar_wrapper, .menu input { width: 0 !important; }' +
  1072. '#reload_btn, #close_btn { width: 52px; }' +
  1073. '#font_info td:first-of-type { width: 33.33%; }' +
  1074. '#font_info td:last-of-type { width: 66.66%; }' +
  1075. 'body.has_hidden_sidebar #content_pane, #font_info { width: 100% !important; }' +
  1076. 'body:not(.has_hidden_sidebar) #sidebar_wrapper { width:'+ getQuery('width').toString() +'%; }' +
  1077. 'body:not(.has_hidden_sidebar) #content_pane { width:'+ (100 - getQuery('width')).toString() +'%; }' +
  1078. '#content_video { width: calc(100% - 2em); }' +
  1079. 'html, body, :root { max-width: 100%; }' +
  1080. '#content_pane:not(.has_zoom_image) #content_image { max-width: calc(100% - 5em); }' +
  1081. '#content_pane.has_zoom_image #content_image { max-width: none; }' +
  1082. '#sidebar_menus td:nth-of-type(odd) { max-width: 24px; }' +
  1083. '#content_grid div img { max-width:'+ ($settings.grid_image_size).toString() +'px; }' +
  1084. 'body.show_numbers #tbody tr td.name a::before { min-width: 17px; }' +
  1085. 'body:not(.has_hidden_sidebar) #sidebar_menus td:nth-of-type(odd)'+
  1086. '{ min-width: 24px; }' +
  1087. 'body:not(.has_hidden_sidebar) #dir_list { min-width: 100px; }' +
  1088. 'body:not(.has_hidden_sidebar) #sidebar_wrapper { min-width: 220px; }' +
  1089. '#sidebar_wrapper, #content_pane { will-change: width }' +
  1090. '#content_header td button { word-break: none; }' +
  1091. '#title { word-break: break-word; }' +
  1092. '#content_pane { z-index: 0; }' +
  1093. '#handle, #sidebar_wrapper, #glyph_viewer, #font_info { z-index: 1; }' +
  1094. '#parents_dir_menu + ul, #content_header { z-index: 2; }' +
  1095. '#sidebar_header { z-index: 3; }' +
  1096. '#bookmarks_menu + ul, #toggle_sidebar { z-index: 9997; }' +
  1097. '#overlay { z-index: 9998; }' +
  1098. '#warnings { z-index: 9999; }' +
  1099. 'body.has_hidden_sidebar #sidebar_header { z-index:unset; }'
  1100. ;
  1101. // Gecko Styles:
  1102. const $gecko_style_rules =
  1103. 'html, body.is_gecko { border: solid 1px gray !important; }' +
  1104. 'body.is_gecko button { padding:revert; }' +
  1105. 'body.is_gecko #grid_btn .menu { top:-7px; left:-120px; }' +
  1106. 'body.is_gecko thead { font-size:100%; }' +
  1107. 'body.is_gecko #dir_list .dir::before { position:absolute; }' +
  1108. 'body.is_gecko.use_default_icons:not(.is_converted_list) #dir_list .file .name .icon'+
  1109. '{ padding-left:4px; background:none; }' +
  1110. 'body.is_gecko.use_default_icons #dir_list .file .name .icon img'+
  1111. '{ margin-right:6px; height:14px; }' +
  1112. 'body.is_gecko #tbody > tr > td:not(:first-of-type) { float:left }' +
  1113. 'body.is_gecko #content_audio_title td { padding-top: 6px; }' +
  1114. 'body.is_gecko #content_audio_title td { padding-bottom: 0; }' +
  1115. 'body.is_gecko #prev_track,body.is_gecko #next_track, body.is_gecko #close_audio'+
  1116. '{ background-color: rgba(26,26,26,.8); }' +
  1117. 'body.is_gecko #close_audio::after { filter:invert(100%); opacity:0.5; }' +
  1118. 'body.is_gecko #close_audio:hover::after { opacity:0.75; }' +
  1119. 'body.is_gecko.dark #prev_track, body.is_gecko.dark #next_track'+
  1120. '{ background: #222 '+ SVG_UI_Icon('next_track_arrow_gecko') +' center no-repeat; }' +
  1121. 'body.is_gecko #prev_track:hover, body.is_gecko #next_track:hover'+
  1122. '{ background: #222 '+ SVG_UI_Icon('next_track_arrow_gecko_hover') +' center no-repeat; }'
  1123. ;
  1124. var $text_editing_style_rules =
  1125. 'html, body, #iframe_body { margin:0; padding:0; }' +
  1126. // toolbar
  1127. '#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; }' +
  1128. '#toolbar li { margin:4px; padding:4px; width:3.5em; display:block; opacity:0.5; list-style-type:none; cursor:pointer; }' +
  1129. '#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'+
  1130. '{ opacity:1; }' +
  1131. '#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; }' +
  1132. '#toolbar li#sync_scroll { width:8em; float:left; opacity:1; }' +
  1133. '#toolbar li#sync_scroll input { float:left; }' +
  1134. '#toolbar li#sync_scroll label { width:8em; display:block; font-size:inherit; line-height:1.5; }' +
  1135. '#toolbar li#clear_text { height:16px; float:right; text-align:center; }' +
  1136. '#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; }' +
  1137. '#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; }' +
  1138. '#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; }' +
  1139. '.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; }' +
  1140. '#toolbar li#save_btn div { display:none; position:relative; top:-9px; left:-24px; color:#999; text-align:right; }' +
  1141. '#toolbar li#save_btn:hover div { display:block; }' +
  1142. '#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; }' +
  1143. '#toolbar li#save_btn span:first-of-type { border-bottom-width:0px; }' +
  1144. '#toolbar li#save_btn span:hover { color:#444; }' +
  1145. // resize handle
  1146. '.split_view #text_editing_handle { width:9px; position:absolute; top:0; bottom:0; left:calc(50% - 4px); cursor:col-resize; z-index:11; }' +
  1147. '#text_editing_handle { z-index:-1; }' +
  1148. // source
  1149. '#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*/,'') +'; }' +
  1150. '#content_source, #content_preview { position:absolute; top:32px; right:0; bottom:0; left:0; }' +
  1151. '#content_source:focus { outline:none; background:#FFF; box-shadow: inset 0 0 4px #888; }' +
  1152. // preview
  1153. '#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*/,'') +'; }' +
  1154. // split views
  1155. '.preview_text:not(.split_view) #content_source, .source_text:not(.split_view) #content_preview, li#save_btn div, .comment'+
  1156. '{ display:none; }' +
  1157. '.split_view #content_preview { border-left:solid 1px #999; }' +
  1158. '.split_view #content_source { width:50%; }' +
  1159. '.split_view #content_preview { left:50%; }' +
  1160. // preview styles
  1161. '#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*/,'') +'; }' +
  1162. '#content_preview .no_list { list-style:none; }' +
  1163. '#content_preview > .no_list { padding:0; }' +
  1164. '#content_preview .text-align-center { text-align:center; }' +
  1165. '#content_preview th, #content_preview td { vertical-align:top; }' +
  1166. '#content_preview blockquote { margin-top:1em; margin-bottom:1em; color: #555; }' +
  1167. '#content_preview blockquote + blockquote { margin-top:0; }' +
  1168. '.markdown-body input[type="checkbox"] { margin-top:0.375em; margin-right:6px; float:left; }' +
  1169. 'h1 .uplink,h2 .uplink,h3 .uplink,h4 .uplink,h5 .uplink,h6 .uplink'+
  1170. '{ margin:0; padding:0; display:inline-block; font-size:0.875em; cursor:pointer; transition: opacity 0.25s; opacity:0; }' +
  1171. 'h1:hover .uplink,h2:hover .uplink,h3:hover .uplink,h4:hover .uplink,h5:hover .uplink,h6:hover .uplink'+
  1172. '{ transition: opacity 0.25s; opacity:0.5; }' +
  1173. '#content_preview table { font-size:inherit; }' +
  1174. '#content_preview table th { background-color:#EEE; }' +
  1175. '.markdown-body::before, .markdown-body::after { display:none !important; background:transparent; }' +
  1176. // edited warning
  1177. '#iframe_body #warnings p, #iframe_body #warnings button { display:none; }' +
  1178. '#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; }' +
  1179. '#iframe_body #warnings h3 { margin:0; font-weight:normal; }' +
  1180. '#iframe_body #warnings div { text-align:right; }' +
  1181. '#iframe_body #warnings button { margin-left:0.5em; }' +
  1182. '#iframe_body #warnings #warning_ignore_btn { margin-right:2em; }' +
  1183. '#iframe_body.has_warning #warnings, #iframe_body.has_warning #warnings.unsaved p#warning_unsaved, #iframe_body.has_warning #warnings.clear p#warning_clear'+
  1184. '{ display:block; }' +
  1185. '#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'+
  1186. '{ display:inline-block; }' +
  1187. // disabled text editing
  1188. '#iframe_body.enable_text_editing #toolbar, #iframe_body.enable_text_editing #preview_text, #iframe_body.enable_text_editing #text_editing_handle'+
  1189. '{ display:none; }' +
  1190. '#iframe_body.enable_text_editing #content_source.disabled'+
  1191. '{ top:0; background:white; }'
  1192. ;
  1193. var $iframe_styles =
  1194. '#iframe_body a.up, #iframe_body #dir_list { background: transparent; }' +
  1195. '#iframe_body :root, html, body { background-color: #BBB; }' +
  1196. '#iframe_body #thead, #tbody tr { background-color: rgba(221,221,221,0.5); }' +
  1197. '#iframe_body #thead tr#parent { background-color: rgba(144,144,144,0.33); }' +
  1198. '#iframe_body #tbody tr:nth-of-type(odd) { background-color: rgba(221,221,221,1); }' +
  1199. '#iframe_body #tbody tr:not(.media):hover { background-color: rgba(172,202,235,0.75) !important; }' +
  1200. '#iframe_body #tbody tr.media:hover { background-color: rgba(116,190,190,0.5) !important; }' +
  1201. '#iframe_body #dir_list tr td.name a::before { content:""; width:14px; height:14px; margin-right:4px; bottom:-1px; background-position:center; background-repeat:no-repeat; background-size:contain; }' +
  1202. CSS_UI_Icon_Rules('#iframe_body') + // background icons
  1203. '#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'+
  1204. '{ background-image:'+ SVG_UI_Icon('check_mark') +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  1205. '#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'+
  1206. '{ background-image:'+ SVG_UI_Icon('chevron_up') +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  1207. '#iframe_body .sorting.down span::after { transform:rotate(180deg) }' +
  1208. ':root, html, #iframe_body { border:0 !important; }' + // border
  1209. 'html, #iframe_body { border-radius:0; }' +
  1210. '#iframe_body #dir_list { border-collapse:collapse; }' +
  1211. '#iframe_body #dir_list thead th { border-bottom:solid 1px #999; }' +
  1212. '#iframe_body, iframe_body td.name, iframe_body td.details'+
  1213. '{ box-sizing:border-box; }' + // box-sizing
  1214. '#iframe_body a { color:#111111; }' + // color
  1215. '#iframe_body a:hover { color:#000000; }' +
  1216. '#iframe_body tr.ignore, #iframe_body tr.ignore a { color:grey; }' +
  1217. '.sorting span::before,.sorting span::after { content:""; width:16px; height:8px; display:inline-block; position:relative; }' +// content
  1218. '#iframe_body tbody { counter-reset: row; }' + // counter
  1219. '#iframe_body.show_numbers tr td.name::before { counter-increment: row; content:counter(row); margin-right:6px; min-width:1.25em; text-align:right;}' +
  1220. '#iframe_body #dir_list td.name::before, #iframe_body #dir_list a::before, td.size, td.date'+
  1221. '{font-variant-numeric: tabular-nums; }'+
  1222. '#iframe_body #dir_list th.sorting { cursor: pointer; }' + // cursor
  1223. '#iframe_body #dir_list tr::before, #iframe_body #dir_list td.name input, #iframe_body td.ext'+
  1224. '{ display:none; }' + // display
  1225. '#iframe_body a { display:block; }' +
  1226. '#parent + tr { display: grid; grid-gap:0; grid-template-columns: 50% 50%; }' +
  1227. '#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); }' +
  1228. '#iframe_body td.name, #iframe_body #sort_by_name, #iframe_body #sort_by_ext'+
  1229. '{ grid-column: 1; }' +
  1230. '#iframe_body td.size, #iframe_body #sort_by_default, #iframe_body #sort_by_size'+
  1231. '{ grid-column: 2; }' +
  1232. '#iframe_body td.date, #iframe_body #sort_by_date { grid-column: 3; }' +
  1233. '#iframe_body td.kind, #iframe_body #sort_by_kind { grid-column: 4; }' +
  1234. '#iframe_body #dir_list td.name::before, #iframe_body #dir_list a::before'+
  1235. '{ float:left; }' + // float
  1236. '#iframe_body { font-family:'+ $settings.UI_font +'; }' + // fonts
  1237. '#iframe_body { font-size:'+ parseFloat($settings.UI_font_size) * 0.875 + $settings.UI_font_size.replace(/\d*/,'') +'; }' +
  1238. '#iframe_body #dir_list, #iframe_body #thead { font-size: 1em; }' +
  1239. 'td.name, td.name a::before, td.size, td.date { font-variant-numeric: tabular-nums; }'+
  1240. '#iframe_body, #iframe_body #dir_list { height:100%; }' + // height
  1241. '#iframe_body #content_source { height:inherit; }' +
  1242. '#iframe_body #thead { height:2em; max-height:2em; }' +
  1243. '#iframe_body #tbody tr { line-height:1.5; }' + // line-height
  1244. 'html, body, #iframe_body { margin:0; }' + // margin
  1245. '#iframe_body .dir::before, #iframe_body .file > img{ margin-inline-end: 6px; }' +
  1246. '#iframe_body #dir_list tr, #iframe_body #tbody tr:hover { outline:0; }' + // outline
  1247. '#iframe_body, #iframe_body #dir_list, .details.date{ overflow:hidden; text-overflow: ellipsis; }' + // overflow
  1248. '#iframe_body #dir_list #tbody { overflow:auto; }' +
  1249. 'html, :root, #iframe_body, body { padding:0; }' + // padding
  1250. '#iframe_body #tbody tr td, #iframe_body #thead tr th { padding-top:3px; }' +
  1251. '#iframe_body #tbody tr td { padding-right: 6px; }' +
  1252. '#iframe_body #tbody tr td, #iframe_body #thead tr th { padding-bottom:3px; }' +
  1253. '#parent th, #iframe_body #tbody tr td.details.kind { padding-right: 1em; }' +
  1254. '#iframe_body td.details { padding-left: 6px; }' +
  1255. '#parent th, #tbody tr td.name { padding-left:1em; }' +
  1256. '#iframe_body #dir_list, #iframe_body #dir_list tr td.name a::before'+
  1257. '{ position:relative; }' + // position
  1258. '#iframe_body, #iframe_body #dir_list #tbody { position:absolute; right:0; bottom:0; left:0; }' +
  1259. '#iframe_body th { text-align:center; }' + // text-align
  1260. '#iframe_body th:last-of-type, #iframe_body td.details { text-align:right; }' +
  1261. '#iframe_body th:first-of-type { text-align:left; }' +
  1262. '#iframe_body a, #iframe_body a:hover, #iframe_body td:hover'+
  1263. '{ text-decoration:none !important; }' + // text-decoration
  1264. '#iframe_body a.icon { text-indent:2px; }' + // text-indent
  1265. '#iframe_body #thead { user-select:none; -webkit-user-select:none; }' +
  1266. '#iframe_body td.details { vertical-align:top; }' +
  1267. 'html, #iframe_body, #iframe_body #dir_list, #iframe_body #dir_list tr'+
  1268. '{ width:100%; max-width:100%; min-width:100%; }' +
  1269. '#iframe_body #dir_list td:not(:first-child) { width:unset; }' +
  1270. '#iframe_body td:not(.name) { white-space:pre; }'
  1271. ;
  1272.  
  1273. // ADD STYLES
  1274. const $font_styles = document.createElement('style');
  1275. const $font_grid_styles = document.createElement('style');
  1276.  
  1277. function addStyles(el) {
  1278. el.find('style, link[rel="stylesheet"], link[href$="css"]').remove(); // remove any existing stylesheets
  1279. el.append('<style>'+ $main_style_rules +'</style>');
  1280. el.append('<style>'+ $gecko_style_rules +'</style>');
  1281. el.append($font_styles); // empty until font is previewed
  1282. el.append($font_grid_styles); // empty until font grid is made
  1283. el.append('<style>'+ $text_editing_style_rules +'</style>');
  1284. el.append('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>');
  1285. }
  1286. // ***** END STYLES ***** //
  1287.  
  1288. // ***** INDEX PREP ***** //
  1289. //
  1290. function getIndexType() {
  1291. // Try to determine index type from parent directory link container,
  1292. // with fallbacks for indexes that don't have parent directories,
  1293. // or for parent directory links that aren't siblings or ancestors of the index itself.
  1294. let parentLinkParent; // Possible elements: pre, li, td, th, div -- used to determine index type
  1295. // Try to find parent directory link:
  1296. let parentLink = $('a:contains("Parent Directory"), a:contains("parent directory"), a[href="../"], a[href="/"], a[href^="?"], img[alt*="PARENTDIR"]');
  1297. if ( parentLink.length === 0 ) {
  1298. parentLinkParent = $('body').find('> ul li, > pre, > table:last-of-type tr td');
  1299. } else {
  1300. parentLinkParent = parentLink.parent(); // use original found parentLink
  1301. }
  1302. // If no parentLinkParent found, type = error; else return parent node name
  1303. let nodeName = ( parentLinkParent[0] !== undefined ? parentLinkParent[0].nodeName.toLowerCase() : '');
  1304. if ( parentLinkParent.length === 0 ) {
  1305. nodeName = 'error';
  1306. } else if ( $protocol.startsWith('file') ) {
  1307. if ( navigator.userAgent.indexOf('Firefox') > 0 ) {
  1308. nodeName = 'gecko';
  1309. }
  1310. }
  1311. let types = {'gecko':'gecko','li':'list','pre':'pre','th':'table','td':'table','div':'default','error':'error'};
  1312. let type = types[nodeName];
  1313.  
  1314. if ( type === 'table' ) {
  1315. parentLinkParent.closest('table').addClass('PARENTTABLE');
  1316. }
  1317. if ( type === 'list' ) {
  1318. parentLink.closest('ul').addClass('PARENTLIST');
  1319. }
  1320. return type;
  1321. }
  1322.  
  1323. // Return Index items, Index type, remove parent directory link, and add body class.
  1324. function getIndexItems(el) {
  1325. const type = getIndexType();
  1326. let items;
  1327.  
  1328. switch(type) {
  1329. case 'gecko':
  1330. $(el).addClass('is_converted_gecko');
  1331. items = $('body').find('> table > tbody');
  1332. break;
  1333. case 'list':
  1334. $(el).addClass('is_converted_list');
  1335. items = $('body').find('> ul');
  1336. break;
  1337. case 'pre':
  1338. $(el).addClass('is_converted_pre');
  1339. items = $('body').find('> pre').html();
  1340. break;
  1341. case 'table':
  1342. case 'td':
  1343. $(el).addClass('is_converted_table');
  1344. if ( $('table.PARENTTABLE > tbody').length === 1 ) {
  1345. items = $('table.PARENTTABLE > tbody');
  1346. } else {
  1347. items = $('table.PARENTTABLE'); // tables without tbody
  1348. }
  1349. break;
  1350. case 'default': // local chrome default
  1351. $(el).addClass('is_default');
  1352. items = $('body').find('> table').find('> tbody');
  1353. break;
  1354. case 'error': // error
  1355. $(el).addClass('is_error');
  1356. items = $('body').html();
  1357. break;
  1358. }
  1359. return [items,type];
  1360. }
  1361.  
  1362. // Index Prep: convert rows and return array of rows, with link, size, date-modified
  1363. function convertIndexItems(type,items) {
  1364. let converted = [];
  1365. switch(type) {
  1366. case 'gecko':
  1367. converted = convertGeckoType(items);
  1368. break;
  1369. case 'list':
  1370. converted = convertListType(items);
  1371. break;
  1372. case 'pre':
  1373. converted = convertPreType(items);
  1374. break;
  1375. case 'table':
  1376. case 'default': // local chrome indexes
  1377. converted = convertTableType(type,items);
  1378. break;
  1379. case 'error':
  1380. converted = convertErrorType(items);
  1381. break;
  1382. }
  1383. return converted;
  1384. }
  1385. // Index Prep: convert list type function
  1386. function convertGeckoType(items) {
  1387. let preppedIndex = [];
  1388. const rows = Array.from(items.find('> tr'));
  1389. for ( let row of rows ) {
  1390. let preppedRow = [];
  1391. let cellContents = '';
  1392. let cells = Array.from( $(row).find('> td') );
  1393. let link = ($(cells).find('a').attr('href'));
  1394. for ( let cell of cells ) {
  1395. cellContents = cell.innerText;
  1396. cellContents = ( cellContents !== undefined ? cellContents.trim() : '');
  1397. preppedRow.push(cellContents);
  1398. }
  1399. preppedRow[1] = preppedRow[1].replace(' KB','000'); // convert reported size in KB to total bytes
  1400. preppedRow[2] = preppedRow[2] + ' '+ preppedRow[3];
  1401. preppedRow = preppedRow.slice(1,-1);
  1402. if ( link.length > 0 && link !== '/' && link !== '../' ) {
  1403. preppedRow.unshift(link);
  1404. }
  1405. if ( preppedRow.length > 0 ) { preppedIndex.push(preppedRow); }
  1406. }
  1407. return preppedIndex;
  1408. }
  1409. // Index Prep: convert list type function
  1410. function convertListType(items) {
  1411. let preppedIndex = [];
  1412. const rows = Array.from(items.find('li'));
  1413. for ( let row of rows ) {
  1414. if ( row.innerHTML.indexOf('Parent Directory') === -1 ) {
  1415. let preppedRow = [];
  1416. let link = $(row).find('a').attr('href');
  1417. row = row.innerHTML.replace(/<a .+?<\/a>\s*/,'');
  1418. let cells = row.split(' ');
  1419. for ( let cell of cells ) {
  1420. if ( cell.trim().length > 0 ) {
  1421. preppedRow.push(cell);
  1422. }
  1423. }
  1424. if ( link.length > 0 && link !== '/' && link !== '../' ) {
  1425. preppedRow.unshift(link);
  1426. }
  1427. if ( preppedRow.length > 0 ) {
  1428. preppedIndex.push(preppedRow);
  1429. }
  1430. }
  1431. }
  1432. return preppedIndex;
  1433. }
  1434. // Index Prep: convert pre type function
  1435. function convertPreType(items) {
  1436. let preppedIndex = [];
  1437. items = items.replace(/<a /g,' <a ').replace(/<\/a>/g,'</a> ') // ensure two spaces after file links
  1438. .replace(/\[\s*?\]/g,'[]'); // remove empty 'alt=[ ]'
  1439. const rows = items.split('\n');
  1440. const spaces = /\s{2,}/; // assumes pre type only uses spaces between "columns"
  1441.  
  1442. for ( let row of rows ) {
  1443. let preppedRow = [];
  1444. row = row.replace(/(<a[^>]+?>).+?<\/a>/g,'$1'); // remove link display name because some have 2+ spaces, leading to incorrect split for cells array
  1445. let cells = row.split(spaces);
  1446. let link;
  1447. for ( let cell of cells ) {
  1448. if ( cell.trim().length > 0 ) {
  1449. if ( cell.indexOf('href="') > 0 && cell.search(/href="[?|\/"]|Parent Directory/) === -1 ) {
  1450. link = $(cell).attr('href');
  1451. } else {
  1452. if ( cell.search(/href="|<img |<hr>/) === -1 ) {
  1453. preppedRow.push(cell);
  1454. }
  1455. }
  1456. }
  1457. }
  1458. if ( link !== undefined ) { preppedRow.unshift(link); } // add link to front of preppedRow
  1459. if ( preppedRow.length > 1 ) { preppedIndex.push(preppedRow); }
  1460. }
  1461. return preppedIndex;
  1462. }
  1463. // Index Prep: convert table type function
  1464. function convertTableType(type,items) { // for local chrome indexes and server-generated table-type indexes
  1465. let preppedIndex = [];
  1466. const rows = Array.from(items.find('> tr:not(.PARENT)'));
  1467. for ( let row of rows ) {
  1468. if (row.innerText.search(/Parent Directory/) === -1 ) {
  1469. let preppedRow = [];
  1470. let cells = Array.from( $(row).find('td') );
  1471. let link = $(cells).find('a').attr('href');
  1472. for ( let cell of cells ) {
  1473. cell = cell.innerHTML.trim();
  1474. if ( cell.length > 0 && cell.search(/^&nbsp;$|href="|<img /m) === -1 ) {
  1475. preppedRow.push( cell );
  1476. }
  1477. }
  1478. if ( link !== undefined ) { preppedRow.unshift(link); }
  1479. if ( preppedRow.length > 1 ) { preppedIndex.push(preppedRow); } // preppedRow.length > 2 in order to omit parent directory row
  1480. }
  1481. }
  1482. return preppedIndex;
  1483. }
  1484. // Index Prep: convert error pages (page not found, etc.)
  1485. function convertErrorType(items) {
  1486. items = items.replace(/ style="[^"]*?"/g,'').replace(/<hr>/g,'');
  1487. items = '<div id="error_message"><div id="error_message_icon"></div>'+ items +'</div>';
  1488. return items;
  1489. }
  1490.  
  1491. // Index Prep: Build new Index from prepped rows
  1492. function buildNewIndex(preppedIndex,sort) {
  1493. let newIndexItems = [];
  1494. let i = 0;
  1495. for ( let row of preppedIndex ) {
  1496. // Get row attrs and text
  1497. const rowLink = row[0] !== undefined ? row[0].replace(/&amp;/g,'&') : '';//.replace(//,''); // decode some reserved characters
  1498. const rowName = getItemName(rowLink).replace(/^\s/m,'\&nbsp;').replace(/^\//m,'').replace(/([-_——])/g,'$1<wbr>'); // display name, with word breaks added after unbreakable chars
  1499. const rowSortName = getItemName(rowLink).replace(/^\//m,'').replace('/','').toLocaleLowerCase();
  1500. const sizesAndDates = getItemSizeAndDate(row);
  1501. const rowSize = sizesAndDates[0];
  1502. const rowSortSize = sizesAndDates[1];
  1503. const rowDate = sizesAndDates[2];
  1504. const rowSortDate = sizesAndDates[3];
  1505. const rowExt = getItemExt(rowLink);
  1506. const rowSortKind = getItemKind(rowExt);
  1507. const rowKind = rowSortKind.slice(0,1).toUpperCase() + rowSortKind.slice(1);
  1508. const rowClasses = getItemClasses(rowName,rowSortKind,rowExt);
  1509. // Assemble row elements
  1510. let newRow = $('<tr></tr>').attr('id','rowid-'+ i).addClass(rowClasses).attr('data-kind',rowSortKind).attr('data-ext',rowExt);
  1511. const checkbox = ( rowClasses.indexOf('media') > 0 ? $('<input type="checkbox" tabindex="-1" checked="true" />') : '' );
  1512. const nameSpan = $('<span></span>').append(checkbox, rowName);
  1513. const cellLink = $('<a></a>').addClass('icon').attr('href',rowLink).append(nameSpan);
  1514. const cellName = $('<td></td>').addClass('name').attr('data-name',rowSortName).append(cellLink);
  1515. const cellSize = $('<td></td>').addClass('size details').attr('data-size',rowSortSize).append(rowSize);
  1516. const cellDate = $('<td></td>').addClass('date details').attr('data-date',rowSortDate).append(rowDate);
  1517. const cellKind = $('<td></td>').addClass('kind details').attr('data-kind',rowSortKind).append(rowKind);
  1518. const cellExt = $('<td></td>').addClass('ext details').attr('data-ext',rowExt);
  1519. // Assemble row
  1520. newRow.append(cellName, cellSize, cellDate, cellKind, cellExt);
  1521. newIndexItems.push(newRow[0]);
  1522. i++;
  1523. }
  1524. if ( sort === undefined ) { sort = getQuery('sort_by'); }
  1525. let sortedIndexItems = sortDirList($(newIndexItems), 'sort_by_'+ sort, 1); // initial sort
  1526. return sortedIndexItems;
  1527. }
  1528. // Index Prep: get row name
  1529. function getItemName(link) {
  1530. let name;
  1531. try { name = decodeURIComponentSafe(link); }
  1532. catch (error) { name = link.replace(/%20/g,' ').replace(/\s{2,}/,' '); }
  1533.  
  1534. if ( name.split('/').length > 2 ) { // get name only if x = full path
  1535. let arr = name.split('/');
  1536. if ( name.startsWith('/') && name.endsWith('/') ) { // dirs
  1537. name = arr[arr.length - 2] + '/';
  1538. } else {
  1539. name = arr[arr.length - 1]; // files
  1540. }
  1541. }
  1542. return name;
  1543. }
  1544. // Index Prep: get row classes
  1545. function getItemClasses(name,kind,ext) {
  1546. let itemClasses = [];
  1547. itemClasses.push(kind);
  1548. if ( ext.search(/dir|app/) === -1 ) {
  1549. itemClasses.push('file');
  1550. }
  1551. if ( ext === 'app' ) {
  1552. if ( $settings.apps_as_dirs === false ) {
  1553. itemClasses.push('file');
  1554. } else {
  1555. itemClasses.push('dir');
  1556. }
  1557. }
  1558. if ( name.endsWith('symlink') || name.endsWith('alias') || name.endsWith('symbolic link') ) {
  1559. itemClasses.push('alias');
  1560. }
  1561. if ( name.indexOf('.') === 0 ) {
  1562. itemClasses.push('invisible');
  1563. }
  1564. if ( !JSON.stringify($row_types).match( escapeStr(ext) ) ) { // else classify as "other" if extension is not in $row_types.
  1565. itemClasses.push('other'); //itemType = 'Other'; itemKind = 'other';
  1566. } else if ( kind === '' ) {
  1567. // itemClasses.push('code'); //itemType = 'Code'; itemKind = 'code';
  1568. } else {
  1569. itemClasses.push(ext);
  1570. }
  1571. if ( $row_settings.ignore.includes( ext ) ) {
  1572. itemClasses.push('ignore');
  1573. }
  1574. if ( $row_settings.exclude.includes( ext ) ) {
  1575. itemClasses.push('exclude');
  1576. }
  1577. if ( kind === 'audio' || kind === 'video' ) {
  1578. itemClasses.push('media');
  1579. }
  1580. itemClasses = Array.from(new Set(itemClasses)); // remove dupe classes
  1581. return itemClasses.join(' ');
  1582. }
  1583. // Index Prep: get formatted row size and date
  1584. function getItemSizeAndDate(cells) {
  1585. let sizesAndDates = [];
  1586. let rowDisplaySize, rowSortSize, rowDisplayDate, rowSortDate;
  1587. if ( cells.length > 1 ) {
  1588. if ( cells[1].search(/[-:\/]/) !== -1 ) { // test for typical date/time separators.
  1589. rowDisplayDate = cells[1];
  1590. rowDisplaySize = cells[2];
  1591. } else {
  1592. rowDisplayDate = cells[2];
  1593. rowDisplaySize = cells[1];
  1594. }
  1595. }
  1596. // size
  1597. let sizeUnits = /[BYTES|B|K|KB|MB|GB|TB|PB|EB|ZB|YB]/;
  1598. if ( rowDisplaySize === undefined || rowDisplaySize === '' || rowDisplaySize === '-' || rowDisplaySize === ',' ) {
  1599. rowDisplaySize = '&mdash;'; rowSortSize = '0'; // if no size supplied, use these defaults
  1600. } else {
  1601. rowSortSize = getItemSortSize(rowDisplaySize);
  1602. rowDisplaySize = ( rowDisplaySize.toUpperCase().endsWith('B') ? rowDisplaySize : rowDisplaySize + 'B');
  1603. if ( !rowDisplaySize.toUpperCase().match(sizeUnits) ) { // if provided size is only numeric
  1604. rowDisplaySize = formatBytes(rowDisplaySize,1);
  1605. } else {
  1606. rowDisplaySize = rowDisplaySize.replace('K','k').replace(/(\d+)\s*([A-z])/,'$1 $2');
  1607. }
  1608. }
  1609. // date
  1610. if ( rowDisplayDate === undefined || rowDisplayDate === '' || rowDisplayDate === '-' ) {
  1611. rowDisplayDate = '&mdash;'; rowSortDate = '0';
  1612. } else {
  1613. rowSortDate = getItemDate(rowDisplayDate);
  1614. }
  1615. sizesAndDates.push(rowDisplaySize,rowSortSize,rowDisplayDate,rowSortDate);
  1616. return sizesAndDates;
  1617. }
  1618. // Index Prep: get row size for sorting
  1619. function getItemSortSize(val) {
  1620. let sortSize;
  1621. let values = val.replace(/(\d+)\s*([A-z])/,'$1 $2').split(' ');
  1622. let size = values[0];
  1623. let unit = values[1];
  1624. if ( unit !== undefined ) { unit = unit.toUpperCase(); }
  1625. 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
  1626. sortSize = size * factor[unit]; // convert byte size to multiplication factor
  1627. return sortSize;
  1628. }
  1629. // convert numeric sizes to display format
  1630. function formatBytes(val, decimals) {
  1631. if (val === 0) return '0 Bytes';
  1632. const k = 1024;
  1633. const dm = decimals < 0 ? 0 : decimals;
  1634. const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  1635. const i = Math.floor(Math.log(val) / Math.log(k));
  1636. return parseFloat((val / Math.pow(k, i)).toFixed(dm)) +' '+ sizes[i];
  1637. }
  1638. // process date
  1639. function processDate(match,p1,p2,p3) { //date formats: 2017-10-09 13:12 || 2015-07-25T02:02:57.000Z || 12-Mon-2017 21:11
  1640. const mo = 'JanFebMarAprMayJunJulAugSepOctNovDec'.indexOf(p2)/3 + 1; // e.g., convert month into number, or use number
  1641. return p3 +'-'+ mo +'-'+ p1;
  1642. }
  1643. // Index Prep: get row date 2015-07-25T02:22:00.000Z
  1644. function getItemDate(val) {
  1645. let sortDate = val.replace(/^(\d{2})-(\w{3})-(\d{4})/m, processDate) // convert Month to number
  1646. .replace(/\b(\d{1})[-:\/]/g,'0$1/') // add leading 0 for single digit numbers
  1647. .replace(/(\d{2})\/(\d{2})\/(\d{2}),/,'$3$1$2') // reorder MM/DD/YY dates
  1648. .replace(/-|:|\s+|\//g,''); // remove spacing characters
  1649. return sortDate;
  1650. }
  1651. // Index Prep: get row kind
  1652. function getItemKind(ext) {
  1653. let kind = '';
  1654. if ( ext === 'dir' || ext === 'app' ) {
  1655. kind = ext;
  1656. } else {
  1657. for ( let types in $row_types ) {
  1658. if ( $row_types[types].includes( ext ) ) {
  1659. kind = types;
  1660. return kind;
  1661. }
  1662. }
  1663. }
  1664. if ( kind === '' ) { kind = 'other'; }
  1665. return kind;
  1666. }
  1667. // Index Prep: get row extension
  1668. function getItemExt(link) {
  1669. let ext = '';
  1670. if ( link.endsWith('app/') || link.endsWith('exe') ) {
  1671. ext = 'app';
  1672. } else if ( link.endsWith('/') ) {
  1673. ext = 'dir';
  1674. } else if ( link.indexOf('.') < 0 ) { // if no '.' in link, ...
  1675. ext = link.slice(link.lastIndexOf('/') + 1).toLowerCase();
  1676. } else { // find the last . and get the remaining characters
  1677. ext = link.slice(link.lastIndexOf('.') + 1).toLowerCase();
  1678. }
  1679. return ext;
  1680. }
  1681. // END INDEX PREP
  1682. // ***** MAKE NEW INDEX ***** //
  1683. function makeNewIndex(el,sort) {
  1684. const indexItems = getIndexItems(el);
  1685. const items = indexItems[0];
  1686. const type = indexItems[1];
  1687. const convertedIndex = convertIndexItems( type, items ); // = array of rows: ["link","date","size"]
  1688. if ( type === 'error' ) {
  1689. return [convertedIndex];
  1690. } else {
  1691. let newIndex = buildNewIndex( convertedIndex, sort );
  1692. return [newIndex];
  1693. }
  1694. }
  1695. function appendNewIndex(body_top) { // setUpUI();
  1696. const newIndex = makeNewIndex(body_top);
  1697. $dir_list_body.empty().append( newIndex);
  1698. $dir_list.find('#stats').html(getIndexStats($dir_list.find('#tbody tr')));
  1699. }
  1700. // get and set index stats
  1701. function getIndexStats(el) {
  1702. const total = el.length;
  1703. const dirs = el.filter('.dir').length;
  1704. const files = el.filter('.file').length;
  1705. const stats = total +' items: '+ dirs +' directories, '+ files +' files';
  1706. return stats;
  1707. }
  1708. // ***** END DIR_LIST SETUP ***** //
  1709.  
  1710. // ***** UI SETUP ***** //
  1711. // Build UI: Append all assembled elements to $body
  1712. function buildUI() {
  1713. if ( window.self === window.top ) { // if it's an iframe...
  1714. $('head').prepend('<meta charset="utf-8">');
  1715. $('head #title').removeAttr('id'); // not sure where this id is being added, but it must be removed...
  1716. $('body').attr('id','top').attr('lang','en').find('script').remove();
  1717. addStyles( $('head') );
  1718. appendNewIndex($('body'));
  1719. $('body').empty().append($main_content);
  1720. } else {
  1721. $('body').attr('id','iframe_body');
  1722. }
  1723. }
  1724. buildUI();
  1725.  
  1726. // Define additional UI Element refs
  1727. const $body = $('body#top');
  1728. const $iframe_body = $('body#iframe_body');
  1729. const $iFrame_head = $iframe_body.prev('head');
  1730. const $dir_list_row = $dir_list_body.find('> tr');
  1731. const $audio_files = $dir_list_body.find('.audio');
  1732. const $font_files = $dir_list_body.find('.font');
  1733. const $image_files = $dir_list_body.find('.image');
  1734. const $media_files = $dir_list_body.find('.media');
  1735. const $video_files = $dir_list_body.find('.video');
  1736. function $selected_file() { return $dir_list_body.find('.selected'); }
  1737. function $playing_file() { return $dir_list_body.find('.playing'); }
  1738.  
  1739. // UI Setup: build UI and add body classes and other initial settings
  1740. function setupUIprefs() {
  1741. for ( let key in $settings ) {
  1742. if ( getQuery(key) === 'true' ) {
  1743. $body.addClass(key);
  1744. } else { // non-boolean settings and others
  1745. if ( key === 'theme' ) { $body.addClass( 'theme_'+ getQuery('theme') ); }
  1746. if ( key === 'sort_by' ) {
  1747. $body.addClass( 'sort_by_'+ getQuery(key) );
  1748. $('#sorting').find('th[id="sort_by_'+ getQuery(key) +'"]').addClass('up');
  1749. }
  1750. if ( key === 'default_text_view' ) {
  1751. if ( getQuery(key) === 'source_text' ) { $body.addClass('source_text'); } else { $body.addClass('preview_text'); }
  1752. }
  1753. if ( getQuery('toggle_sidebar') === 'true' ) { $body.addClass( 'has_hidden_sidebar' ); }
  1754. }
  1755. }
  1756. if ( navigator.userAgent.indexOf('Chrome') > 0 ) { $body.addClass('is_chrome'); }
  1757. if ( navigator.userAgent.indexOf('Firefox') > 0 ) { $body.addClass('is_gecko'); }
  1758. if ( $audio_files.length > 0 ) { $body.add($dir_list).addClass('has_audio has_media'); }
  1759. if ( $font_files.length > 0 ) { $body.addClass('has_fonts'); }
  1760. if ( $image_files.length > 0 ) { $body.addClass('has_images'); }
  1761. if ( $video_files.length > 0 ) { $body.add($dir_list).addClass('has_video has_media'); }
  1762. // UI Setup: show invisibles
  1763. if ( navigator.platform.indexOf('Win') > -1 || $location.indexOf('file:') < 0 ) {
  1764. $('#show_invisibles').closest('label').hide();
  1765. } else if ( $body.hasClass('show_invisibles') ) {
  1766. $('#show_invisibles').attr('checked','checked');
  1767. }
  1768. }
  1769. // Get DOMTokenList of body classes
  1770. function getClassList(id) { return document.getElementById(id).classList; }
  1771. //
  1772. function setUpUI() {
  1773. if ( window.self === window.top) {
  1774. setupUIprefs();
  1775. initMedia();
  1776. autoSelectFile();
  1777. autoLoadCoverArt();
  1778. setContentTitle();
  1779. setContentHeight();
  1780. } else {
  1781. setUpIframeUI( getQuery('enable_text_editing') );
  1782. }
  1783. }
  1784. setUpUI();
  1785.  
  1786. // SET UI TO DEFAULT SETTINGS: remove queries;
  1787. function defaultSettings() {
  1788. let $location = window.location.href;
  1789. $location = $location.slice(0,$location.lastIndexOf('?'));
  1790. window.location.assign($location);
  1791. }
  1792. $('#default_settings').on('click', function() {
  1793. 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.' ) ) {
  1794. defaultSettings();
  1795. }
  1796. });
  1797. // EXPORT SETTINGS
  1798. function saveSettings(filename, data) {
  1799. const blob = new Blob([data], {type: 'text/html'});
  1800. const elem = window.document.createElement('a');
  1801. elem.href = window.URL.createObjectURL(blob);
  1802. elem.download = filename;
  1803. document.body.appendChild(elem);
  1804. elem.click();
  1805. document.body.removeChild(elem);
  1806. URL.revokeObjectURL(blob);
  1807. }
  1808. $('#export_settings').on('click','a',function(e) {
  1809. e.preventDefault();
  1810. const $settings_string = ( JSON.stringify($settings,null,'\t'));
  1811. saveSettings('settings.txt',$settings_string);
  1812. });
  1813.  
  1814. // Click Menu Link (with warning)
  1815. function setLocation(link) { window.location = link; }
  1816. $('#parent_dir_menu,#parents_dir_menu + .menu,#bookmarks').on('click','a',function(e) {
  1817. e.preventDefault();
  1818. if ( $(this).attr('href').indexOf('file://') > -1 && $protocol !== 'file:' ) {
  1819. $body.addClass('has_warning').find('#warnings').addClass('local');
  1820. } else {
  1821. showWarning( 'setLocation', thisLink($(this)) );
  1822. }
  1823. });
  1824. // Show Menus
  1825. function showMenus(el) {
  1826. let $position = $(el).position();
  1827. $(el).find('> ul').css({'top':$position.top + $(el).innerHeight() + 'px'}).toggle().parent('td').siblings('td').find('.menu').hide();
  1828. }
  1829. $('#parents_dir_menu').add('#bookmarks_menu').parent('td').on('click',function(e) {
  1830. e.stopPropagation();
  1831. showMenus(this);
  1832. });
  1833. // Hide Menus
  1834. $(document).on('click', function() { $('.menu').hide(); });
  1835.  
  1836. // Toggle UI Preferences
  1837. function toggleUIpref(prefID) {
  1838. let prefClass = prefID;
  1839. if ( prefID === 'split_view' ) { sendMessage('iframe','split_view'); }
  1840. if ( prefID.startsWith('sort_by') ) {
  1841. $body.removeClass('sort_by_default sort_by_name sort_by_size sort_by_date sort_by_kind sort_by_ext');
  1842. }
  1843. if ( prefID.startsWith('theme') ) {
  1844. prefID = ( $body.hasClass('theme_dark') ? 'theme_dark' : 'theme_light' );
  1845. prefClass = 'theme_dark theme_light';
  1846. }
  1847. if ( prefID.endsWith('text') ) {
  1848. prefClass = 'preview_text source_text';
  1849. sendMessage('iframe','default_text_view');
  1850. }
  1851. toggleQuery(prefID);
  1852. $body.toggleClass(prefClass);
  1853. setContentHeight();
  1854. if ( prefID === 'enable_text_editing' ) { $('.selected.text, .selected.markdown').click(); }
  1855. $('#bookmarks').hide(); // don't hide menus after click?
  1856. }
  1857. // Click Toggle UI Pref elements
  1858. $('.toggle_UI_pref').on('click',function(e) {
  1859. if ( !$(this).is('input') ) {
  1860. e.preventDefault(); // allow checkboxes to be checked
  1861. }
  1862. toggleUIpref( $(this).attr('id') );
  1863. });
  1864.  
  1865. // Toggle Sidebar
  1866. function toggleSidebar() {
  1867. $body.toggleClass('has_hidden_sidebar');
  1868. if ( $body.hasClass('has_hidden_sidebar') ) { setQuery('toggle_sidebar','true'); } else { removeQuery('toggle_sidebar'); }
  1869. setContentHeight();
  1870. scrollThis('tbody','selected',true); // true = instant scroll
  1871. }
  1872. $('#toggle_sidebar').on('click', function() {
  1873. toggleSidebar();
  1874. });
  1875.  
  1876. // RESIZE Sidebar/Content Pane
  1877. function resizeSidebar(f) {
  1878. f.stopPropagation();
  1879. const $sidebar_wrapper = $('#sidebar_wrapper');
  1880. const $startX = f.pageX;
  1881. let $window_width = window.innerWidth;
  1882. let $sidebar_width = $sidebar_wrapper.width();
  1883. $('#overlay').css({'display':'block','z-index':'1'});
  1884. $('#handle').css({'z-index':'9999'});
  1885. $body.css({'-moz-user-select':'none','user-select':'none'});
  1886.  
  1887. $(document).on('mousemove',function(e) {
  1888. e.stopPropagation();
  1889. e.preventDefault();
  1890. const $deltaX = e.pageX - $startX;
  1891. if ( e.pageX > 230 && e.pageX < $window_width - 200 ) {
  1892. $sidebar_wrapper.css({'width':$sidebar_width + $deltaX + 'px'});
  1893. $content_pane.css({'width':($window_width - $sidebar_width) - $deltaX + 'px'});
  1894. }
  1895. setContentHeight();
  1896. });
  1897. $(document).on('mouseup',function() {
  1898. $('#overlay').css({'display':'none','z-index':'unset'});
  1899. $('#handle').css({'z-index':'unset'});
  1900. $body.css({'-moz-user-select':'unset','user-select':'unset'});
  1901. $(document).off('mousemove');
  1902. $sidebar_width = $sidebar_wrapper.width();
  1903. setQuery('width',$sidebar_width);
  1904. });
  1905. }
  1906. $('#handle').on('mousedown', function(f) {
  1907. f.stopPropagation();
  1908. resizeSidebar(f);
  1909. });
  1910.  
  1911. // ***** BASIC UI FUNCTIONS ***** //
  1912. // Get dir_list item row
  1913. function thisRow(x) {
  1914. return $(x).closest('#dir_list > tbody > tr').length > 0 ? $(x).closest('#dir_list > tbody > tr') : $(x).closest('#dir_list > li');
  1915. }
  1916. function thisID(x) {
  1917. return thisRow(x).attr('id');
  1918. }
  1919. // Get row link
  1920. function thisLink(x) {
  1921. return $(x).find('a').length > 0 ? $(x).find('a').attr('href') : $(x).attr('href');
  1922. }
  1923. // Get row name
  1924. function thisText(x) {
  1925. return $(x).find('a span').length > 0 ? decodeURIComponentSafe( $(x).find('a span').text().toLocaleLowerCase() ) : decodeURIComponentSafe( $(x).text().toLocaleLowerCase() );
  1926. }
  1927. // get row text
  1928. function thisExt(x) {
  1929. let $this_name = thisText(x);
  1930. return $this_name.endsWith('app/') ? 'app' : $this_name.endsWith('/') ? '/' : $this_name.lastIndexOf('.') === -1 ? undefined : $this_name.toLocaleLowerCase().slice( $this_name.lastIndexOf('.') + 1 );
  1931. }
  1932. function getElById(id) {
  1933. return $(document.getElementById(id) );
  1934. }
  1935.  
  1936. // SET CONTENT HEIGHT
  1937. function setContentHeight() {
  1938. let $title = $('#title');
  1939. // accommodate multi-line title names in preview pane
  1940. if ( $title.outerWidth() > ( $('#content_title').innerWidth() - 2 * $('#title_buttons_left').outerWidth() ) ) {
  1941. $title.css({'margin-top':'2em'});
  1942. } else {
  1943. $title.css({'margin-top':'0'});
  1944. }
  1945. let $window_height = window.innerHeight;
  1946. let $content_header_height = $('#content_header').outerHeight();
  1947.  
  1948. $('#sidebar').add($main_content).add($content_pane).css({'height':$window_height });
  1949. $dir_list.css({'height':$window_height - $('#sidebar_header').outerHeight(), 'max-height':$window_height - $('#sidebar_header').outerHeight() });
  1950. $dir_list_body.css({'bottom': $dir_list.find('tfoot').outerHeight(),'height': $dir_list.innerHeight() - $dir_list.find('tfoot').outerHeight(),'max-height': $dir_list.innerHeight() - $dir_list.find('tfoot').outerHeight() });
  1951. $('#iframe_body').find('#tbody').css({'top':$('#iframe_body').find('#thead').height() + 1 +'px'});
  1952. $content_font.css({'padding-bottom': $('#font_info th').outerHeight() +'px' });
  1953.  
  1954. $('#prev_track, #next_track, #close_audio').css({'height':$('#audio').height() }); // set height of audio controls
  1955. $('#content_container').add('#glyph_viewer').css({'top':$content_header_height +'px' });
  1956. }
  1957. window.addEventListener('resize', setContentHeight );
  1958.  
  1959. // SET CONTENT TITLE
  1960. function setContentTitle() {
  1961. let $title = $('#title');
  1962. let $title_text = '';
  1963. if ( $playing_file().hasClass('audio') ) { // set audio player title
  1964. $('#content_audio_title').find('td').empty().text( $playing_file().find('td.name a').text() );
  1965. }
  1966. if ( $content_pane.hasClass('has_grid') ) { // set main title for other content
  1967. $title_text = $('#parents_dir_menu').find('> div').html().split('<wbr>').reverse()[1];
  1968. } else if ( $('#toggle_info').hasClass('selected') ) {
  1969. $title_text = 'Source of: '+ $current_dir_path;
  1970. } else if ( $body.hasClass('has_text') ) {
  1971. $title_text = 'Text Editor';
  1972. } else if ( $selected_file().hasClass('audio') ) {
  1973. return;
  1974. } else if ( !$selected_file().hasClass('audio') || $content_pane.hasClass('has_hidden_text') ) {
  1975. $title_text = $selected_file().not('.audio').find('td.name a span').text(); // Assemble title
  1976. }
  1977. if ( $content_pane.hasClass('has_image') ) {
  1978. setDimensions($('.selected.image'));
  1979. } else {
  1980. $title.attr('data-after',''); // remove image dimensions
  1981. }
  1982. $title.empty().html($title_text);
  1983. if ( $title.outerWidth() > ( $('#content_title').innerWidth() - 2 * $('#title_buttons_left').outerWidth() ) ) { $title.css({'margin-top':'2em'}); } else { $title.css({'margin-top':'0'}); }
  1984. }
  1985. // Get Image Dimensions
  1986. function getDimensions(link, callback) {
  1987. let img = new Image();
  1988. img.src = link;
  1989. img.onload = function() { callback( this.width, this.height ); };
  1990. }
  1991. function setDimensions(row) {
  1992. getDimensions( thisLink(row), function( width, height ) {
  1993. $('#title').attr('data-after',' (' + width + 'px × ' + height + 'px)');
  1994. });
  1995. }
  1996. // Scroll Selected Items
  1997. function scrollThis(elID,className,bool) {
  1998. let $behavior = 'smooth';
  1999. if ( bool !== undefined ) { $behavior = 'instant'; }
  2000. let $block = ( navigator.userAgent.indexOf('Firefox') > -1 ? 'start' : 'nearest' );
  2001. if ( document.getElementsByClassName(className).length > 0 ) {
  2002. document.getElementById(elID).getElementsByClassName(className)[0].scrollIntoView({ behavior:$behavior, block:$block, inline:'nearest' });
  2003. }
  2004. setContentHeight();
  2005. }
  2006.  
  2007. // ***** SORTING ***** //
  2008. function sortIndex(els,id,sortDirection) { // id = sort type
  2009. let sorted = [];
  2010. const newSort = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); // needs to be above makeNewIndex()
  2011. sorted = els.removeClass('sorted').sort((a, b) => {
  2012. let aName, bName;
  2013. if ( $('body').hasClass('has_playlist') ) {
  2014. aName = $(a).attr('id');
  2015. bName = $(b).attr('id');
  2016. } else {
  2017. aName = $(a).find('td.name').data('name');
  2018. bName = $(b).find('td.name').data('name');
  2019. }
  2020. let aData = $(a).find('td[data-'+ id +']').data(id);
  2021. let bData = $(b).find('td[data-'+ id +']').data(id);
  2022. if ( sortDirection === 1 ) { // sort by detail data value, then by name (i.e., if data values are the same, sort by name)
  2023. if ( newSort.compare(aData, bData) === 0 ) {
  2024. return newSort.compare(aName, bName);
  2025. } else {
  2026. return newSort.compare(aData, bData);
  2027. }
  2028. } else { // reverse sort
  2029. if ( newSort.compare(bData, aData) === 0 ) {
  2030. return newSort.compare(bName, aName);
  2031. } else {
  2032. return newSort.compare(bData, aData);
  2033. }
  2034. }
  2035. });
  2036. sorted = sorted.sort((a, b) => { // add sorted style (rules between different rows)
  2037. if ( id === 'kind' || id === 'ext' && sortDirection === 1 ) {
  2038. if ( $(a).find('td[data-'+ id +']').data(id) !== $(b).find('td[data-'+ id +']').data(id) ) { $(a).addClass('sorted'); }
  2039. }
  2040. if ( id === 'kind' || id === 'ext' && sortDirection === -1 ) {
  2041. if ( $(b).find('td[data-'+ id +']').data(id) !== $(a).find('td[data-'+ id +']').data(id) ) { $(a).addClass('sorted'); }
  2042. }
  2043.  
  2044. });
  2045. return sorted;
  2046. }
  2047. // Sort the Dir List on click
  2048. function sortDirList(rows, id, sortDirection) {
  2049. const $sort_all = rows;
  2050. const $sort_dirs = $sort_all.filter('.dir:not(.app)');
  2051. const $sort_files = $sort_all.filter('.file,.app');
  2052. const $sort_id = id.slice(8);// === 'default' ? 'name' : id;
  2053. let $sorted = [];
  2054.  
  2055. if ( $sort_id === 'default' || ( $sort_id !== 'name' && $settings.dirs_on_top === true )) {
  2056. const $sorted_dirs = sortIndex($sort_dirs, $sort_id, sortDirection);
  2057. const $sorted_files = sortIndex( $sort_files, $sort_id, sortDirection );
  2058. if (sortDirection === 1) {
  2059. $sorted = $.merge($sorted_dirs,$sorted_files);
  2060. } else {
  2061. $sorted = $.merge($sorted_files,$sorted_dirs);
  2062. }
  2063. } else {
  2064. $sorted = sortIndex( $sort_all, $sort_id, sortDirection );
  2065. }
  2066. return $sorted;
  2067. }
  2068. // Sort on click
  2069. function clickSort(id) {
  2070. const el = $(getElById(id)); // id from th sort item = 'sort_by_xxx'
  2071. let sortDirection = ( el.hasClass('up') ? -1 : 1 ); // Only reverse sort order after clicking a sort column once.
  2072. // SORT EM!
  2073. let rows = ( $body.hasClass('has_playlist') ? $('#tbody').find('tr') : $dir_list_row );
  2074. const $sorted_index = sortDirList( rows, id, sortDirection );
  2075. $dir_list_body.empty().append($sorted_index);
  2076. $('div[id="'+ id +'"]').toggleClass('up').siblings().removeClass('up');
  2077. if ( $content_grid.hasClass('has_font_grid') ) { $('#show_font_grid').click(); }
  2078. if ( $content_grid.hasClass('has_image_grid') ) { $('#show_image_grid').click(); }
  2079. if ( $content_grid.hasClass('has_grid') ) { $('#grid_btn').click(); }
  2080. setContentHeight();
  2081. }
  2082. // Sort on clicking dir_list sort item
  2083. $('#sorting').on('click','div.sorting:not(.disabled)',function(e) {
  2084. e.preventDefault();
  2085. e.stopPropagation();
  2086. clickSort( $(this).attr('id') );
  2087. });
  2088. // Sort Menu: click the list header that contains the selected menu text.
  2089. $('#sort_menu').on('click','li:not(.disabled)',function(e) {
  2090. e.preventDefault();
  2091. e.stopPropagation();
  2092. $('#sorting div[id="sort_by_'+ $(this).attr('id') +'"]').click(); // click corresponding sidebar sort item
  2093. });
  2094. // ***** END SORTING ***** //
  2095. // ***** END BASIC UI FUNCTIONS ***** //
  2096.  
  2097. // ***** CONTENT PANE ***** //
  2098. // SELECT ROW on click and set classes for $content_pane
  2099. function selectThis(row) {
  2100. $('#toggle_info').removeClass('selected');
  2101. 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();
  2102. if ( $content_pane.hasClass('has_grid') ) { // Select corresponding grid item
  2103. row.addClass('selected').siblings().removeClass('selected hovered');
  2104. $grid_selected.addClass('selected').siblings().removeClass('selected');
  2105. } else { // remove classes from rows, but leave .audio with playing
  2106. if ( row.attr('id') === 'toggle_info' ) {
  2107. row.toggleClass('selected loaded');
  2108. $dir_list.find('#tbody .selected').removeClass('selected');
  2109. } else {
  2110. row.addClass('selected').siblings().removeClass('selected hovered');
  2111. }
  2112. }
  2113. }
  2114.  
  2115. //***** SHOW CONTENT *****//
  2116. function showAudio(rowID) {
  2117. let row = getElById(rowID);
  2118. if ( $content_video.hasClass('has_content') ) { $('#title').empty(); }
  2119. closeMedia('video');
  2120. if ( row.hasClass('audio') ) { row.addClass('playing selected').siblings('.media').removeClass('playing selected'); }
  2121. $audio_player.attr('src', thisLink( row ) );
  2122. $content_pane.addClass('has_audio');
  2123. $('#content_audio_title').find('td').empty().text( $playing_file().find('td.name a').text() );
  2124. setContentHeight();
  2125. }
  2126.  
  2127. // showTextEditor();
  2128.  
  2129. // Show Grid
  2130. function showGrid() {
  2131. $content_pane.removeClass('has_hidden_grid').addClass('has_grid');
  2132. if ( $body.hasClass('has_text') ) { $body.toggleClass('has_text has_hidden_text'); }
  2133. closeMedia('video');
  2134. setContentTitle();
  2135. setContentHeight();
  2136. selectThis($selected_file());
  2137. }
  2138. // Show Hidden Editor or Grid
  2139. function showHiddenEditorOrGrid() {
  2140. if ( $content_pane.hasClass('has_hidden_grid') && !$body.hasClass('has_hidden_text') || $content_pane.hasClass('has_hidden_grid') && $body.hasClass('has_hidden_text') ) {
  2141. $content_pane.toggleClass('has_grid has_hidden_grid'); // show grid preferentially
  2142. } else if ( !$content_pane.hasClass('has_hidden_grid') && $body.hasClass('has_hidden_text') ) {
  2143. $body.toggleClass('has_text has_hidden_text');
  2144. }
  2145. }
  2146. // Hide Editor or Grid
  2147. function hideEditorOrGrid() {
  2148. if ( $body.hasClass('has_text') ) { $body.toggleClass('has_text has_hidden_text'); }
  2149. if ( $content_pane.hasClass('has_grid') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  2150. }
  2151. // showIndexSource();
  2152.  
  2153. // Show Font (and create font items)
  2154. function showFont(row,bool,sheet) { // bool = true if for show font grid, sheet defined at fontGridItems()
  2155. let fontStyles = $font_styles.sheet;
  2156. const $font_family = thisText(row);
  2157. const $font_url = thisLink(row);
  2158. const $font_grid_item_el = $('<div class="font_grid_item"></div>');
  2159. if ( bool === false ) {
  2160. if ( fontStyles.cssRules.length > 0 ) { fontStyles.deleteRule(0); } // delete previous @font-face rule
  2161. fontStyles.insertRule('@font-face { font-family: "'+ $font_family +'"; src: url("'+ $font_url +'"); }');
  2162. $content_font.css({ 'font-family':'"'+ $font_family +'"' }); // set content font style
  2163. } else {
  2164. let grid_item = $font_grid_item_el.clone();
  2165. const $display_name = $font_family;
  2166. sheet.insertRule('@font-face { font-family: "'+ $font_family +'"; src: url("'+ $font_url +'"); }');
  2167. grid_item.append('<p>'+ $display_name.toUpperCase() +'</p><h2 style=\'font-family: "'+ $font_family +'"\'; ><a href="'+ $font_url +'">'+ $display_name.slice(0,$font_family.lastIndexOf('.')) +'</a></h2>' );
  2168. return grid_item;
  2169. }
  2170. }
  2171.  
  2172. // OPEN FONT
  2173. /// opentype.js font parsing
  2174. $('#open_font_label').on('click',function(e) { $('.menu').hide(); });
  2175. // Open font
  2176. $('#bookmarks').on('change','#open_font',function(e) {
  2177. openFont(e);
  2178. });
  2179. function openFont(evt) {
  2180. if (window.File && window.FileReader && window.FileList && window.Blob && window.opentype ) {
  2181. let files = evt.target.files[0];
  2182. let reader = new FileReader();
  2183. reader.readAsArrayBuffer(files);
  2184. reader.onload = function(file) {
  2185. closeOtherContent();
  2186. hideEditorOrGrid();
  2187. $content_pane.addClass('has_font');
  2188. $content_font.empty().addClass('has_content');
  2189. parseFont(reader.result);
  2190. return true;
  2191. };
  2192. $('#open_font').val(''); // reset input to allow same font to be reopened immediately after closing.
  2193. } else {
  2194. alert('File APIs are not fully supported in this browser or opentype.js is not available.');
  2195. }
  2196. }
  2197. // Parse font
  2198. function parseFont(fontblob) {
  2199. let font = window.opentype.parse(fontblob);
  2200. getFontInfo(font);
  2201. let $glyphs_container = $('<div id="glyphs_container"></div>');
  2202. let $glyph_container = $('<div class="glyph_container"></div>');
  2203. let $glyph_canvas = $('<canvas class="glyph" width="120" height="120"></canvas>');
  2204. let $glyph_info = $('<div class="glyph_info" style="font-size:0.75rem"></div>');
  2205. let $glyph_viewer = $('<div id="glyph_viewer"><div id="glyph_viewer_info" style="height:18px;line-height:1.6;"><button id="save_svg_hidden" style="visibility:hidden;float:left;">Save SVG</button><div></div><button id="save_svg" style="float:right;">Save SVG</button></div></div>');
  2206. $glyph_viewer.data('data-font-name',font.names.fullName.en);
  2207. let glyphs = font.glyphs;
  2208. $content_font.data('data-glyphs',glyphs).append($glyphs_container).prepend($glyph_viewer); // add glyphs data to $content_font
  2209. // Draw glyphs
  2210. for ( let i = 0; i < glyphs.length; i++ ) {
  2211. let glyph = glyphs.glyphs[i];
  2212. // GLyph width
  2213. let boundingBox = glyph.getBoundingBox();
  2214. let glyphWidth = boundingBox.x2 - boundingBox.x1;
  2215. let contextX = (60 - glyphWidth/24);
  2216. // Add glyph info and append elements
  2217. let glyphUnicode = ( glyph.unicode !== undefined ? '#'+ glyph.unicode : glyph.unicode );
  2218. let glyphContainer = $glyph_container.clone();
  2219. let glyphCanvas = $glyph_canvas.clone();
  2220. let glyphInfo = $glyph_info.clone();
  2221. glyphInfo.text(glyph.index +': '+ glyph.name +', '+ glyphUnicode);
  2222. glyphContainer.append( glyphCanvas.clone().attr('id','glyph_'+ glyph.index ) ).append(glyphInfo);
  2223. $glyphs_container.append( glyphContainer );
  2224. // Draw glyph
  2225. let thisGlyph = document.getElementById('glyph_'+ glyph.index);
  2226. $(thisGlyph).data('contextX',contextX);
  2227. let context = thisGlyph.getContext('2d');
  2228. glyph.draw(context, contextX, 84, 72);
  2229. }
  2230. $('#title').empty().html(font.names.fullName.en);
  2231. setContentHeight();
  2232. }
  2233. // Get font info
  2234. function getFontInfo(font) {
  2235. let $font_names = font.names;
  2236. let $font_info = $('<table id="font_info"><thead><tr><th colspan=2>FONT INFO: '+ font.names.fullName.en.toUpperCase() +'</th></tr></thead><tbody></tbody></table>');
  2237. for (let name in $font_names) {
  2238. let value = $font_names[name].en;
  2239. if ( name.endsWith('URL') ) {
  2240. let href = value;
  2241. if ( !value.startsWith('http') ) {
  2242. href = 'http://'+ value;
  2243. }
  2244. value = '<a href="'+ href +'" target="_blank">'+ value +'</a>';
  2245. }
  2246. $font_info.find('tbody').append('<tr><td>'+ name +': </td><td>'+ value +'</td></tr>');
  2247. }
  2248. let numGlyphs = font.numGlyphs;
  2249. $font_info.find('tbody').append('<tr><td>numGlyphs: </td><td>'+ numGlyphs +'</td></tr>');
  2250. $content_font.prepend($font_info);
  2251. }
  2252. // Show glyph viewer
  2253. $content_font.on('click','.glyph',function(e) {
  2254. e.stopPropagation();
  2255. let id = $(this).attr('id');
  2256. showGlyph( id );
  2257. });
  2258. function showGlyph(id) {
  2259. let $glyph_viewer = $('#glyph_viewer');
  2260. let glyphX = $('#'+ id).data('contextX');
  2261. id = id.slice(id.lastIndexOf('_') + 1);
  2262. let glyphs = $content_font.data('data-glyphs'); // get glyphs
  2263. let glyph = glyphs.get(id);
  2264. let glyphName = glyph.name;
  2265. let glyphPath = glyph.getPath(glyphX,84,72);
  2266. let glyphSVG = glyphPath.toSVG().replace(/"/g,'\'');
  2267. // set the background SVG image:
  2268. let SVGprefix = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' x=\'0px\' y=\'0px\' width=\'120px\' height=\'120px\' viewBox=\'0 0 120 120\' style=\'enable-background:new 0 0 120 120;\' xml:space=\'preserve\' ><g>';
  2269. $glyph_viewer.show().css({'background-image': SVGprefix + glyphSVG +'</g></svg>")'});
  2270. $glyph_viewer.data('data-raw-svg',glyphSVG).data('data-glyph-name',glyphName).find('#glyph_viewer_info div').text(glyphName); // for saving SVG
  2271. $content_font.addClass('has_glyph');
  2272. setContentHeight();
  2273. }
  2274. // Save glyph svg
  2275. $content_font.on('click','#save_svg',function(e) {
  2276. e.stopPropagation();
  2277. saveGlyph();
  2278. });
  2279. function saveGlyph() {
  2280. let filename = $('#glyph_viewer').data('data-font-name') +'—'+ $('#glyph_viewer').data('data-glyph-name') +'.svg';
  2281. let data_prefix = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="120px" height="120px" viewBox="0 0 120 120" style="enable-background:new 0 0 120 120;" xml:space="preserve" ><g>';
  2282. let data_suffix = '</g></svg>';
  2283. let data = data_prefix + $('#glyph_viewer').data('data-raw-svg') + data_suffix;
  2284. let blob = new Blob([data], {type: 'image/svg+xml'});
  2285. let downloadEl = window.document.createElement('a');
  2286. downloadEl.href = window.URL.createObjectURL(blob);
  2287. downloadEl.download = filename;
  2288. document.body.appendChild(downloadEl);
  2289. downloadEl.click();
  2290. document.body.removeChild(downloadEl);
  2291. URL.revokeObjectURL(blob);
  2292. }
  2293.  
  2294. // Set Content Pane classes
  2295. function setContentClasses(content_el) {
  2296. if ( content_el === 'audio' ) {
  2297. $content_pane.addClass('has_audio');
  2298. } else {
  2299. hideEditorOrGrid();
  2300. $content_pane.addClass( 'has_'+ content_el ); // remove classes, excluding grid and text classes; add content class
  2301. }
  2302. }
  2303. // Set Content Element Sources and row classes
  2304. function setContentSources(row,link,kind,content_el) {
  2305. switch(kind) {
  2306. case 'audio':
  2307. $audio_player.attr('src',link);
  2308. break;
  2309. case 'font':
  2310. showFont(row,false);
  2311. break;
  2312. case 'code':
  2313. case 'markdown':
  2314. case 'text':
  2315. link = thisLink(row) +'?split_view='+ getQuery('split_view') +'&default_text_view='+ getQuery('default_text_view');
  2316. if ( !$body.hasClass('enable_text_editing') ) { link += '&enable_text_editing=false'; } else { link += '&enable_text_editing=true'; }
  2317. break;
  2318. case 'dir':
  2319. link = link + '?sort_by=' + getQuery('sort_by') +'&show_numbers='+ getQuery('show_numbers') +'&use_custom_icons='+ getQuery('use_custom_icons');
  2320. break;
  2321. case 'info':
  2322. link = $location + '?sort_by=' + getQuery('sort_by') +'&show_numbers='+ getQuery('show_numbers') +'&view_source=true';
  2323. break;
  2324. case 'pdf':
  2325. link = link + '?#view=fitB&scrollbar=1&toolbar=1&navpanes=1';
  2326. break;
  2327. }
  2328. if ( kind === 'video' ) { row.addClass('playing'); } else { row.addClass('loaded'); }
  2329. // fix for pdfs not being loaded after setting src attribute (hide and then show)
  2330. if ( kind === 'pdf' ) {
  2331. $content_pdf.hide().addClass('has_content').attr('src','').attr('src',link).show().removeAttr('srcdoc');
  2332. } else {
  2333. $('#content_'+ content_el).addClass('has_content').attr('src',link).removeAttr('srcdoc');
  2334. }
  2335. }
  2336. // Show Content
  2337. function showContent(row) { // show any content excluding grids on row click
  2338. if ( row.length === -1 ) { return; } // needed for left/right arrow nav when there are no valid items (i.e. images or fonts)
  2339. let rowID = row.attr('id');
  2340. let kind = row.attr('data-kind');
  2341. let link = thisLink(row);
  2342. 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
  2343. if ( row.hasClass('audio') ) {
  2344. showAudio( rowID );
  2345. } else {
  2346. closeOtherContent(row);
  2347. setContentClasses(content_el);
  2348. setContentSources(row,link,kind,content_el);
  2349. setContentTitle();
  2350. setContentHeight();
  2351. row.siblings().removeClass('loaded');
  2352. }
  2353. }
  2354.  
  2355. //***** CLOSE CONTENT (Close button or Cmd/Ctrl + W) *****//
  2356. // Close Audio/Video
  2357. function closeMedia(type) { // type === audio || video
  2358. let $mediaEl = ( type === 'audio' ? $audio_player : $content_video );
  2359. $mediaEl.trigger('pause').attr('src','');
  2360. $content_pane.removeClass('has_'+ type);
  2361. $('.media.playing, .media.loaded').removeClass('playing loaded');
  2362. if ( type === 'audio' ) {
  2363. $('#content_audio_title td').empty();
  2364. }
  2365. if ( type === 'video' ) {
  2366. $('#content_video').removeClass('has_content');
  2367. }
  2368. setContentHeight();
  2369. }
  2370. // Close Audio button click
  2371. $('#close_audio').on('click',function(e) {
  2372. e.stopPropagation();
  2373. closeMedia('audio');
  2374. $('.audio.selected').removeClass('selected');
  2375. if ( $('.loaded').length === 1 ) { $('.loaded').addClass('selected'); }
  2376. });
  2377. // Close Text Editor
  2378. function closeTextEditor() {
  2379. if ( $body.hasClass('edited') ) {
  2380. showWarning('closeContent');
  2381. } else {
  2382. $body.removeClass('has_text');
  2383. if ( $content_pane.hasClass('has_hidden_grid') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  2384. }
  2385. }
  2386. // Close Grid
  2387. function closeGrid() {
  2388. $content_pane.removeClass('has_grid');
  2389. $content_grid.empty().removeClass().attr('style','').find('.image_grid_item, img').attr('style','');
  2390. if ( $body.hasClass('has_hidden_text') ) { $body.toggleClass('has_text has_hidden_text'); }
  2391. }
  2392. // Close index source preview
  2393. function closeIndexSource() {
  2394. $('#toggle_info').removeClass('selected loaded');
  2395. $content_pane.removeClass('has_iframe').find('#content_iframe').removeClass('has_content').removeAttr('src');
  2396. if ( $('.loaded:not(.audio)').length === 1 || $('.playing').length > 0 ) {
  2397. $('.loaded:not(.audio)').click();
  2398. $('.playing').addClass('selected');
  2399. }
  2400. }
  2401. // Close .content elements (not #content_audio, #content_text, or #content_grid) when opening new content
  2402. function closeOtherContent(row) {
  2403. // close audio/video when opening video/audio
  2404. if ( row !== undefined ) {
  2405. if ( row.hasClass('video') ) {
  2406. closeMedia('audio');
  2407. } else if ( row.hasClass('audio') ) {
  2408. closeMedia('video');
  2409. }
  2410. }
  2411. // close other content
  2412. if ( $('.content.has_content').length || $content_pane.hasClass('has_ignored') ) {
  2413. let $contentID = $('.content.has_content').attr('id');
  2414. let $specimen = $content_font.html();
  2415. switch($contentID) { // specific cases
  2416. case 'content_font':
  2417. $content_font.attr('style','').empty().append( $specimen );
  2418. break;
  2419. case 'content_image':
  2420. $content_pane.removeClass('has_zoom_image');
  2421. $content_image.attr('style',''); // reset image: comment out to retain image scale after loading other content
  2422. break;
  2423. case 'content_video':
  2424. closeMedia('video');
  2425. break;
  2426. }
  2427. // remove content classes and source attribute
  2428. $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');
  2429. showHiddenEditorOrGrid();
  2430. }
  2431. }
  2432. // Close Button: close any visible content
  2433. function closeContent() {
  2434. if ( $('#toggle_info').hasClass('selected') ) {
  2435. closeIndexSource();
  2436. } else if ( $body.hasClass('has_text') ) {
  2437. closeTextEditor();
  2438. } else if ( $content_pane.hasClass('has_grid') ) { // close grid (i.e., don't just hide)
  2439. closeGrid();
  2440. } else if ($content_font.hasClass('has_glyph') ) {
  2441. $('#glyph_viewer').hide();
  2442. $content_font.addClass('has_content').removeClass('has_glyph');
  2443. } 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') ) {
  2444. $('.selected:not(.audio)').removeClass('selected');
  2445. $('.loaded').removeClass('loaded');
  2446. closeOtherContent();
  2447. } else if ( $body.hasClass('iframe_edited') ) { // warn if iframe edited
  2448. showWarning('closeContent');
  2449. } else if ( $content_pane.hasClass('has_audio') ) { // close audio last of all
  2450. closeMedia('audio');
  2451. $('.selected.audio').removeClass('selected');
  2452. } else if ( $body.hasClass('has_playlist') ) {
  2453. $body.removeClass('has_playlist');
  2454. $('#parents_dir_menu').find('> div').empty().html($current_dir_path);
  2455. clickSort(getQuery('sort_by'));
  2456. if ( $('#tbody').find('.audio').length < 1 ) { $body.removeClass('has_audio'); }
  2457. }
  2458. if ( $('#font_info').length !== 1 ) { setContentTitle(); }
  2459. setContentHeight();
  2460. }
  2461. $('#close_btn').on('click', function(e) {
  2462. e.preventDefault();
  2463. closeContent(); // close content unless body.edited or body.iframe_edited
  2464. });
  2465.  
  2466. //***** RESET CONTENT (Reset button or Cmd/Ctrl + R) *****//
  2467. function resetContent() {
  2468. if ( $content_pane.attr('class') === '' ) { window.location = window.location.href; } // reload page
  2469. if ( $content_pane.hasClass('has_audio') ) { $audio_player.prop('currentTime', 0).trigger('pause'); }
  2470. if ( $content_pane.hasClass('has_grid') ) { $('#grid_btn').click(); }
  2471. if ( $content_pane.hasClass('has_font') ) { $content_font.css({'font-size':'1em'});}
  2472. if ( $content_pane.hasClass('has_image') ) { $content_pane.removeClass('has_zoom_image').find('#content_image').attr('style',''); }
  2473. if ( $content_pane.hasClass('has_video') ) { $content_video.prop('currentTime',0).trigger('pause'); }
  2474. if ( $content_pane.hasClass('has_iframe') || $content_pane.hasClass('has_dir') ) { $selected_file().find('a').click(); }
  2475. setContentHeight();
  2476. }
  2477. $('#reload_btn').on('click', function(e) {
  2478. e.preventDefault();
  2479. showWarning('resetContent');
  2480. });
  2481.  
  2482. //**********************//
  2483. //***** NAVIGATION *****//
  2484. function firstRowID(className) {
  2485. return ( $('#tbody').find(':visible:not(.unchecked)').filter(className).length ? $('#tbody').find('tr:visible:not(.unchecked)').filter(className).first().attr('id') : null );
  2486. }
  2487. function prevRowID(className) {
  2488. let row;
  2489. if ( ['.audio','.video','.media'].includes(className) ) { row = $playing_file(); } else { row = $selected_file(); }
  2490. 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');
  2491. }
  2492. function nextRowID(className) { // if nothing selected, or if no next row with classname, return first row with classname, else return next row with classname
  2493. let row;
  2494. if ( ['.audio','.video','.media'].includes(className) ) { row = $playing_file(); } else { row = $selected_file(); }
  2495. 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');
  2496. }
  2497. // function lastRowID(className) {
  2498. // return ( $('#tbody').find(':visible:not(.unchecked)').filter(className).length ? $('#tbody').find('tr:visible:not(.unchecked)').filter(className).last().attr('id') : null );
  2499. // }
  2500. function selectRowID(className,key) {
  2501. let id = '';
  2502. if ( key === 'ArrowUp' ) { id = prevRowID('.dir,.file'); }
  2503. if ( key === 'ArrowLeft' ) { id = prevRowID(className); }
  2504. if ( key === 'ArrowRight' ) { id = nextRowID(className); }
  2505. if ( key === 'ArrowDown' ) { id = nextRowID('.dir,.file'); }
  2506. return id;
  2507. }
  2508. // NAVIGATION: select GRID items by left/right arrow keys @ arrowNavigation();
  2509. function gridNavigation(key) {
  2510. let className = '';
  2511. if ( $content_grid.hasClass('has_font_grid') ) { className = '.font:not(.ignore)'; }
  2512. if ( $content_grid.hasClass('has_image_grid') ) { className = '.image:not(.ignore)'; }
  2513. if ( $content_grid.hasClass('has_grid') ) { className = '.font:not(.ignore),.image:not(.ignore)'; }
  2514. selectThis( getElById(selectRowID(className,key) ) );
  2515. }
  2516. // NAVIGATION: FONTS and IMAGES by prev/next buttons
  2517. $content_pane.on( 'click','#prev_btn, #next_btn', function(e) {
  2518. e.stopPropagation();
  2519. e.preventDefault();
  2520. let key = $(this).attr('id') === 'prev_btn' ? 'ArrowLeft' : 'ArrowRight';
  2521. clickRow( selectRowID('.font,.image',key));
  2522. });
  2523. // 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
  2524. function playPrevNextTrack(key) {
  2525. if ( $('.playing').length === 0 ) { // Arrow L/R selects first/last audio file if nothing selected
  2526. clickRow( selectRowID('.media',key) );
  2527. playMedia('play');
  2528. } else if ( $('.playing').length === 1 ) {
  2529. let mediaClass = ( $('.playing').hasClass('audio') ? '.audio' : '.video' );
  2530. // If shuffle play...
  2531. if ( $body.hasClass('shuffle_audio') ) {
  2532. let trackRowID = $audio_player.data('shufflelist').pop();
  2533. if ( trackRowID !== undefined ) { // if shuffle list is not empty...
  2534. showAudio( trackRowID ); // load audio...
  2535. playMedia('play'); // and play
  2536. } else if ( trackRowID === undefined ) { // if end of shufflelist...
  2537. if ( $body.hasClass('loop_audio') ) { // and if loop audio, update the shufflelist and play
  2538. updateShuffleList();
  2539. playMedia('play');
  2540. } else { // else load the first track
  2541. showAudio( firstRowID( mediaClass ));
  2542. }
  2543. }
  2544. // else if there is another media file selected, play it next
  2545. } else if ( $(mediaClass).filter('.selected').length === 1 && !$('.media.selected').hasClass('playing') ) {
  2546. showAudio( $('.media.selected').attr('id') );
  2547. playMedia('play');
  2548. } else {
  2549. if ( $body.hasClass('loop_audio') || nextRowID( mediaClass ) !== firstRowID( mediaClass ) ) {
  2550. showAudio( selectRowID( mediaClass,key ) );
  2551. playMedia('play');
  2552. } else {
  2553. showAudio( selectRowID( mediaClass,key ) );
  2554. }
  2555. }
  2556. }
  2557. }
  2558. // NAVIGATION: Audio by prev/next audio buttons
  2559. $('.prev_next_track_btn').on('click',function() {
  2560. let key = ( $(this).attr('id') === 'prev_track' ? 'ArrowLeft' : 'ArrowRight' );
  2561. playPrevNextTrack(key);
  2562. });
  2563. // NAVIGATION: Prev/Next Audio or Prev/Next File of same data-kind by arrow left/right
  2564. function leftRightArrowNavigation(className,key) {
  2565. if ( $('.selected').hasClass('audio') && $('.playing').length === 1 ) {
  2566. playPrevNextTrack(key);
  2567. } else if ( $('#toggle_info').hasClass('selected') ) { // if previewing directory source
  2568. if ( $('#tbody .loaded').length > 0 ) { clickRow( $('#tbody .loaded').attr('id') ); } else { clickRow( selectRowID('.dir,.file',key) ); }
  2569. } else {
  2570. if ( $('.selected').length === 0 ) { // else select first/last row
  2571. clickRow( selectRowID('.dir,.file',key) );
  2572. } else { // else select prev/next row of same data-kind
  2573. clickRow( selectRowID('.'+ $('.loaded,.video.playing').attr('data-kind'),key) );
  2574. }
  2575. if (className === 'video') { playMedia('play'); }
  2576. }
  2577. }
  2578. function upDownArrowNavigation(key) {
  2579. let row = $(document.getElementById(selectRowID('.dir,.file',key)));
  2580. if ( row.hasClass('audio') && $('.audio.playing').length === 1 ) { // just select audio file if another audio is playing
  2581. selectThis( row );
  2582. } else if ( $('#toggle_info').hasClass('selected') ) {
  2583. if ( $('#tbody .loaded').length > 0 ) { clickRow( $('#tbody .loaded').attr('id') ); } else { clickRow( selectRowID('.dir,.file',key) ); }
  2584. } else {
  2585. clickRow( selectRowID('.dir,.file',key) );
  2586. }
  2587. }
  2588. // NAVIGATE directory index items by arrow up/down or left/right keys, with/without grid @ indexNavigation()
  2589. function arrowNavigation(className,key) {
  2590. if ( key === 'ArrowUp' || key === 'ArrowDown' ) {
  2591. upDownArrowNavigation(key);
  2592. } else { // key === 'ArrowLeft" || key === 'ArrowRight'
  2593. if ( $content_pane.hasClass('has_grid') ) { // Grid navigation: L/R arrow selects images and fonts only
  2594. gridNavigation(key);
  2595. } else {
  2596. leftRightArrowNavigation(className,key);
  2597. }
  2598. }
  2599. if ( $content_pane.hasClass('has_grid') ) {
  2600. scrollThis('content_grid','selected');
  2601. } else {
  2602. scrollThis('tbody','selected');
  2603. }
  2604. }
  2605.  
  2606. // NAVIGATION: Main navigation by arrow keys
  2607. function indexNavigation(key) {
  2608. let className = ( $('.selected[data-kind]') === undefined ? $dir_list.find('tbody tr:visible').first().attr('data-kind') : $('.selected[data-kind]').attr('data-kind') );
  2609. arrowNavigation(className,key);
  2610. }
  2611.  
  2612. // NAVIGATE items by typed string
  2613. var str = '';
  2614. function timeoutID() {
  2615. return window.setTimeout( function() { str = ''; }, 1500 );
  2616. }
  2617. function alphaNav(e) { // Select Dir_List row by typed string; Todo: select next row by nearest letter
  2618. let timer = timeoutID();
  2619. if ( typeof timer === 'number' ) {
  2620. window.clearTimeout( timer );
  2621. timer = 0; // id
  2622. }
  2623. timeoutID();
  2624. str += e.key;
  2625. str = str.toLowerCase();
  2626. if ( $('#dir_list').find('td.name[data-name^="'+ str +'"]').length ) {
  2627. $('#dir_list').find('td.name[data-name^="'+ str +'"]').first().find('a').click();
  2628. scrollThis('tbody','selected');
  2629. // } else {
  2630. // null; // replace this with some sort of fuzzy match function? TBD
  2631. }
  2632. }
  2633. //***** END NAVIGATION *****//
  2634.  
  2635. //***** CLICK TO SELECT/SHOW CONTENT *****//
  2636.  
  2637. // CLICK element by id
  2638. function clickThis(id) {
  2639. let el = $(document.getElementById(id));
  2640. if ( el.find('a').length > 0 ) { el.find('a').click(); } else { el.click(); }
  2641. }
  2642. // CLICK Row
  2643. function clickRow(id) {
  2644. let row = getElById(id);
  2645. selectThis(row);
  2646. showContent(row);
  2647. }
  2648. $body.find('#tbody').on('click','tr', function(e) {
  2649. e.preventDefault();
  2650. e.stopPropagation();
  2651. if ( $(this).hasClass('playing') ) {
  2652. playPauseMedia();
  2653. } else {
  2654. showWarning( 'clickRow', $(this).attr('id') );
  2655. }
  2656. });
  2657. // Show current dir source in $content_iframe
  2658. $('#toggle_info').on('click',function(e) {
  2659. e.stopPropagation();
  2660. if ( $(this).hasClass('selected') ) {
  2661. $('#close_btn').click();
  2662. } else {
  2663. showWarning( 'clickRow', $(this).attr('id') );
  2664. }
  2665. });
  2666.  
  2667. // DOUBLE-CLICK Row to open directory
  2668. function doubleClickRow(id) { // row.dir only
  2669. let row = getElById(id);
  2670. let $query_str = decodeURIComponentSafe(window.location.search);
  2671. if ( $query_str === '' ) { $query_str = '?'; }
  2672. const $current_index = row.prevAll('.dir:visible').length;
  2673. if ( $query_str.indexOf('history') !== -1 ) {
  2674. $query_str = $query_str.replace(/&selected=\d+/,'').replace(/&history=/,'&history='+ $current_index +'+');
  2675. } else {
  2676. $query_str = $query_str.replace(/&selected=\d+/,'') + '&history='+ $current_index;
  2677. }
  2678. window.location = row.find('a').attr('href') + $query_str;
  2679. }
  2680. $('#tbody').find('tr.dir').on('dblclick', function(e) {
  2681. e.preventDefault();
  2682. e.stopPropagation();
  2683. showWarning( 'doubleClickRow', $(this).attr('id') );
  2684. });
  2685.  
  2686. // CLICK grid item
  2687. function gridItemClick(e,el) {
  2688. e.preventDefault();
  2689. clickThis( thisID($dir_list.find('a[href*="'+ thisLink(el) +'"]') ) );
  2690. }
  2691. $content_grid.on('click','div', function(e) {
  2692. gridItemClick(e,$(this));
  2693. });
  2694.  
  2695. // HOVER Grid Item
  2696. $content_grid.on('mouseenter','> div:not(.selected)',function() {
  2697. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).addClass('hovered');
  2698. }).on('mouseleave','> div:not(.selected)',function() {
  2699. thisRow($dir_list.find('a[href*="'+ thisLink(this) +'"]')).removeClass('hovered');
  2700. });
  2701. // HOVER Dir_list_row and highlight corresponding grid item
  2702. $dir_list_row.hover(function() {
  2703. if ( $content_grid.is(':visible') ) {
  2704. $content_grid.find('[href*="'+ thisLink(this) +'"]').closest('div').addClass('hovered');
  2705. }
  2706. }, function() {
  2707. if ( $content_grid.is(':visible') ) {
  2708. $content_grid.find('.hovered').removeClass('hovered');
  2709. }
  2710. });
  2711.  
  2712. //***** AUTOLOAD CONTENT: index files or files from the file shortcut list *****//
  2713. function autoSelectFile() {
  2714. let $query_prefs = getQueryPrefs();
  2715. let $UI_pref_selected = ( $query_prefs.get('selected') === null ? '' : $query_prefs.get('selected') );
  2716. if ( getQuery('file') !== undefined && getQuery('file').length > 0 && window.top == window.self ) { // load individual files
  2717. clickRow( $dir_list.find('a[href*="'+ getQuery('file') +'"]').closest('tr').attr('id') );
  2718. removeQuery('file');
  2719. } else if ( $UI_pref_selected !== '' ) {
  2720. clickRow( $dir_list.find('tr.dir:visible').eq($UI_pref_selected).attr('id') );
  2721. } else if ( $body.hasClass('autoload_index_files') && $dir_list.find( 'a[href*="/index."]').length > 0 ) { // else load index file
  2722. clickRow( $dir_list.find('a[href*="/index."]').closest('tr').attr('id') );
  2723. } else if ( !$body.hasClass('has_media') ) { // else select first non-media item
  2724. // clickRow( $dir_list.find('#tbody tr:visible:not(.media):not(.invisible):not(.ignored)').first(0).attr('id') );
  2725. }
  2726. if ( $body.hasClass('autoload_media') && $body.hasClass('has_media') ) { // else if audio and images, load cover art
  2727. clickRow( firstRowID('.audio,.video') );
  2728. if ( $playing_file() ) { scrollThis('tbody','playing'); }
  2729. }
  2730. if ( $selected_file() ) { scrollThis('tbody','selected'); }
  2731. }
  2732.  
  2733. // Autoload Cover Art
  2734. function getImageNames() {
  2735. let image_names = [];
  2736. $image_files.each(function() {
  2737. let name = $(this).find('td.name').attr('data-name');
  2738. name = name.slice(0,name.lastIndexOf('.'));
  2739. image_names.push( name );
  2740. });
  2741. return image_names;
  2742. }
  2743. function getCoverArtID() {
  2744. const cover_names = ['cover','front'];
  2745. const image_names = getImageNames();
  2746. for ( let cover_name of cover_names ) {
  2747. if ( image_names.includes(cover_name) ) { // file name = a cover name
  2748. return $image_files.eq( image_names.indexOf(cover_name) ).attr('id');
  2749. } else if ( image_names.some( name => name.startsWith(cover_name)) ) { // file name starts with a cover name
  2750. return $image_files.find('td.name[data-name^="'+ cover_name +'"]').closest('tr').attr('id');
  2751. } else if ( image_names.some( name => name.indexOf(cover_name) > 0 ) ) { // file name includes a cover name
  2752. return $image_files.find('td.name[data-name*="'+ cover_name +'"]').closest('tr').attr('id');
  2753. } else { // else use first image
  2754. return $image_files.first().attr('id');
  2755. }
  2756. }
  2757. }
  2758. function autoLoadCoverArt() { // autoload cover art for audio if dir contains audio and images, and audio is loaded and non-image content is not loaded
  2759. let bodyClasses = getClassList('top');
  2760. if ( bodyClasses.contains( 'has_audio','has_images','autoload_media' ) && !getElById( firstRowID('.media') ).hasClass('video') ) {
  2761. let $coverID = getCoverArtID();
  2762. if ( $coverID !== undefined ) {
  2763. let row = getElById($coverID);
  2764. showContent(row);
  2765. row.addClass('loaded');
  2766. $('#title').html(row.find('.name').attr('data-name'));
  2767. setDimensions($('.loaded.image'));
  2768. }
  2769. }
  2770. }
  2771.  
  2772. //***** KEYBOARD EVENTS *****//
  2773. $body.on('keydown',$dir_list,function(e) {
  2774. const $selected = $selected_file();
  2775.  
  2776. if ( $('#content_source').is(':focus') || $('#content_font div').is(':focus') && e.key !== 'Escape' ) {
  2777. return;
  2778. }
  2779. // Disable all keydown events except return and tab when warning is shown
  2780. if ( $('body').hasClass('has_warning') ) {
  2781. if (e.key !== 'Enter' && e.key !== 'Tab' && e.key !== 'Shift' ) {
  2782. e.preventDefault();
  2783. return false;
  2784. }
  2785. if ( e.key === 'Enter' ) {
  2786. e.preventDefault();
  2787. e.stopPropagation();
  2788. $('#warnings').find('button.focus').click();
  2789. }
  2790. if ( e.key === 'Tab' ) {
  2791. e.preventDefault();
  2792. e.stopPropagation();
  2793. if ( $('#warnings').find('button.focus').length === 0 || $('#warnings').find('button.focus').prev('button').length === 0 ) {
  2794. $('#warnings').find('button').last('button').focus().addClass('focus').siblings().removeClass('focus').blur();
  2795. } else {
  2796. $('#warnings').find('button.focus').prev('button').focus().addClass('focus').siblings().removeClass('focus').blur();
  2797. }
  2798. }
  2799. }
  2800.  
  2801. $(':focus').blur(); // Need this to able to select grid items after clicking close button (or other buttons).
  2802.  
  2803. switch ( e.key ) {
  2804. case 'ArrowUp':
  2805. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) { // Cmd/Ctrl + up arrow = go to parent directory
  2806. if ( $('#parents_dir_menu + ul a').length < 1 ) {
  2807. return;
  2808. } else {
  2809. showWarning( 'clickThis', $('#parent_dir_menu').attr('id') );
  2810. }
  2811. break;
  2812. }
  2813. if ( $('*[contentEditable="true"]').is(':focus') ) { // Allow arrow navigation within content_editable elements
  2814. return;
  2815. }
  2816. e.preventDefault();
  2817. showWarning( 'indexNavigation', 'ArrowUp' );
  2818. break;
  2819. case 'ArrowDown':
  2820. 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
  2821. return;
  2822. } else if ( $('*[contentEditable="true"]' ).is(':focus') ) {
  2823. return;
  2824. } else if ( (e.ctrl || e.metaKey) && $selected.hasClass('dir') ) {
  2825. $selected.find('a').trigger('dblclick');
  2826. break;
  2827. }
  2828. e.preventDefault();
  2829. showWarning( 'indexNavigation', 'ArrowDown' );
  2830. break;
  2831. case 'ArrowLeft':
  2832. if ( (e.ctrl || e.metaKey) || (e.ctrl && e.metaKey) ) {
  2833. return;
  2834. } else if ( $('*[contentEditable="true"]').is(':focus') ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable
  2835. return;
  2836. }
  2837. if ( (e.altKey && e.shiftKey) || e.altKey && !e.metaKey && !e.ctrlKey ) { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  2838. mediaSkip(e);
  2839. return;
  2840. } else {
  2841. showWarning( 'indexNavigation','ArrowLeft' );
  2842. }
  2843. break;
  2844. case 'ArrowRight':
  2845. if ( e.metaKey && !e.altKey && !e.shiftKey && $selected.hasClass('dir') ) { // Open dir with Cmd/Ctrl + Right Arrow
  2846. $selected.find('a').trigger('dblclick');
  2847. return;
  2848. }
  2849. if ( (e.ctrl || e.metaKey) || (e.ctrl && e.metaKey) ) { // Ignore Cmd/Ctrl + arrow or if focused element is content editable
  2850. return;
  2851. } else if ( $('*[contentEditable="true"]').is(':focus') || $selected.hasClass('dir ignore') ) {
  2852. return;
  2853. }
  2854. if ( (e.altKey && e.shiftKey) || e.altKey && !e.metaKey && !e.ctrlKey ) { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  2855. mediaSkip(e);
  2856. return;
  2857. } else {
  2858. showWarning( 'indexNavigation','ArrowRight' );
  2859. }
  2860. break;
  2861. case ' ': // space
  2862. if ( $content_pane.hasClass('has_audio') || $content_pane.hasClass('has_video') ) { // Play/pause media (space bar)
  2863. e.preventDefault();
  2864. playPauseMedia();
  2865. } else {
  2866. alphaNav(e);
  2867. }
  2868. break;
  2869. case 'Enter': // Open directories (or ignore)
  2870. if ( $selected.hasClass('app') && $settings.apps_as_dirs === false ) {
  2871. break;
  2872. } else {
  2873. if ( $selected.hasClass('dir') ) {
  2874. $selected.find('a').trigger('dblclick');
  2875. } else if ( $selected.hasClass('playing') || $selected.hasClass('video') ) { // content_audio or content_video
  2876. playPauseMedia();
  2877. } else {
  2878. $selected.click();
  2879. }
  2880. }
  2881. break;
  2882. // Alphabetical navigation
  2883. 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':
  2884. 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':
  2885. 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 'π':
  2886. 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 '≠':
  2887. case '⁄': case '€': case '‹': case '›': case 'fl': case '‡': case '°': case '·': case '‚': case '±': case '¯': case '˘': case '¿':
  2888. case 'ı': case '': case '´': case '˝': case 'ˆ': case '': case '˜': case '‰': case 'ˇ': case '¨': case '◊': case '„': case '˛': case '¸':
  2889. // 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 'ϖ':
  2890. // 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 'ϒ':
  2891. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
  2892. alphaNav(e);
  2893. }
  2894. break;
  2895. case 'd': // Cmd/Ctrl + D: Toggle Details
  2896. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2897. e.preventDefault();
  2898. $('#show_details').click();
  2899. } else {
  2900. alphaNav(e);
  2901. }
  2902. break;
  2903. case 'e': // Cmd/Ctrl + E: Show Text Editor
  2904. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2905. e.preventDefault();
  2906. $('#text_editor_row').find('a').click();
  2907. } else {
  2908. alphaNav(e);
  2909. }
  2910. break;
  2911. case 'g': // Cmd/Ctrl + G: Show image Grid
  2912. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2913. e.preventDefault();
  2914. $('#grid_btn').click();
  2915. } else {
  2916. alphaNav(e);
  2917. }
  2918. break;
  2919. case 'i': // Cmd/Ctrl + I: Toggle Invisibles
  2920. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2921. e.preventDefault();
  2922. $('#show_invisibles').find('input').click();
  2923. } else {
  2924. alphaNav(e);
  2925. }
  2926. break;
  2927. case 'o': // Cmd/Ctrl + Shift + O: Open selected item in new window
  2928. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && e.shiftKey && !e.altKey ) {
  2929. window.open( thisLink($selected_file()) );
  2930. } else {
  2931. alphaNav(e);
  2932. }
  2933. break;
  2934. case 'r': // Cmd/Ctrl + Shift + R: Refresh
  2935. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2936. e.preventDefault();
  2937. $('#reload_btn').click();
  2938. } else {
  2939. alphaNav(e);
  2940. }
  2941. break;
  2942. case 'w': // Close content pane if Close button visible with Cmd/Crtl + W
  2943. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2944. if ( $content_pane.attr('class').indexOf('has_') > -1 || $('#toggle_info').hasClass('selected') || $body.hasClass('has_playlist') ) {
  2945. e.preventDefault();
  2946. $('#close_btn').click();
  2947. }
  2948. } else {
  2949. alphaNav(e);
  2950. }
  2951. break;
  2952. case '\\':
  2953. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2954. e.preventDefault();
  2955. $('#toggle_split_view').click();
  2956. } else {
  2957. alphaNav(e);
  2958. }
  2959. break;
  2960. case '=':
  2961. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2962. e.preventDefault();
  2963. $('#increase').click();
  2964. } else {
  2965. alphaNav(e);
  2966. }
  2967. break;
  2968. case '-':
  2969. if ( (navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey) && !e.shiftKey && !e.altKey ) {
  2970. e.preventDefault();
  2971. $('#decrease').click();
  2972. } else {
  2973. alphaNav(e);
  2974. }
  2975. break;
  2976. case 'tab':
  2977. break;
  2978. case 'Escape':
  2979. $('*:focus').blur();
  2980. break;
  2981. } // end switch
  2982. });
  2983. // ***** END KEYBOARD EVENTS ***** //
  2984.  
  2985. // ***** GRID SETUP ***** //
  2986. // Create Font Grid Items
  2987. function fontGridItems() {
  2988. let $font_grid_items_arr = [];
  2989. let $font_files = $dir_list_body.find('.font');
  2990. let fontGridStyles = $font_grid_styles.sheet;
  2991. for ( let i = 1; i < $font_files.length; i++ ) {
  2992. let newGridItem = showFont($font_files[i],true,fontGridStyles);
  2993. $font_grid_items_arr.push( newGridItem );
  2994. }
  2995. return $font_grid_items_arr;
  2996. }
  2997. // Create Image Grid Items
  2998. function imageGridItems() {
  2999. let $image_grid_items_arr = [];
  3000. let $image_files = $dir_list_body.find('.image');
  3001. for ( let i = 0; i < $image_files.length; i++ ) {
  3002. const $this_link = thisLink($image_files[i]);
  3003. const exts = $row_types.image.filter( ext => $.inArray(ext, $row_settings.ignore) == -1 ); // decide which image files can be displayed
  3004. const $title_name = $this_link.slice($this_link.lastIndexOf('/') + 1);
  3005. if ( $.inArray( thisExt($image_files[i]), exts ) > -1 ) { // if this row file ext is in the image extension array
  3006. let item = '<div class="image_grid_item"><a href="'+$this_link+'"><img src="'+$this_link+'" title="'+$title_name+'" loading="lazy" /></a></div>';
  3007. $image_grid_items_arr.push( item );
  3008. }
  3009. }
  3010. return $image_grid_items_arr;
  3011. }
  3012. // Make Grids
  3013. function makeGrids(id) {
  3014. closeGrid();
  3015. let $title = $('#title');
  3016. $content_pane.removeClass('has_hidden_grid').addClass('has_grid');
  3017. if ( id === 'show_font_grid' || !$body.hasClass('has_images') ) { // only show font grid
  3018. $title.removeClass().addClass('font_grid');
  3019. $content_grid.addClass('has_font_grid');
  3020. $content_grid.append( fontGridItems() );
  3021. } else if ( id === 'show_image_grid' || !$body.hasClass('has_fonts') ) { // only show image grid
  3022. $title.removeClass().addClass('image_grid');
  3023. $content_grid.addClass('has_image_grid');
  3024. $content_grid.append( imageGridItems() );
  3025. } else { // show grid of both images and fonts
  3026. $title.removeClass();
  3027. if ( $body.hasClass() ) { $content_grid.addClass('has_image_grid'); } else { $content_grid.addClass('has_grid'); }
  3028. $content_grid.append( imageGridItems(), fontGridItems() );
  3029. }
  3030. }
  3031. // Click grid button
  3032. $('#sidebar_header').on('click', '#grid_btn, #show_font_grid, #show_image_grid', function(e) {
  3033. e.stopPropagation();
  3034. makeGrids($(this).attr('id'));
  3035. showGrid();
  3036. });
  3037.  
  3038. // ***** SCALE PREVIEWED IMAGES & FONTS ***** //
  3039. // Scale Fonts
  3040. function scaleFonts(incr, y) {
  3041. const $em = parseInt(getComputedStyle(document.body).fontSize); // pts/em
  3042. const getFontSize = function(el) { return parseFloat(el.css('font-size')); };
  3043. if ( y === 'decrease' ) { incr = 1/incr; }
  3044. if ( $content_pane.hasClass('has_grid') && ( $content_grid.hasClass('has_font_grid') || $content_grid.hasClass('has_grid') ) ) {
  3045. $content_grid.css({'font-size':( getFontSize($content_grid)/$em * incr ) +'em'});
  3046. return;
  3047. }
  3048. if ( $content_pane.hasClass('has_font') ) {
  3049. $content_font.css({'font-size':( getFontSize($content_font)/$em * incr ) +'em'});
  3050. return;
  3051. }
  3052. }
  3053. // Scale Images
  3054. function scaleImages(incr, y) {
  3055. if (y === 'decrease' ) { incr = 1/incr; }
  3056. if ( $content_pane.hasClass('has_image') || $content_pane.hasClass('has_zoom_image') && !$content_pane.hasClass('has_grid') ) {
  3057. var $image_width = Math.round( $content_image.width() ) * incr; // increment image size
  3058. var $image_height = Math.round( $content_image.height() ) * incr; // increment image size
  3059. $content_pane.removeClass('has_zoom_image').addClass('has_image');
  3060. $content_image.css({'width':$image_width +'px', 'height': $image_height +'px', 'max-width':'none', 'max-height':'none' });
  3061. $('#content_container').scrollLeft( ( $image_width - $(window).width() )/2 ) ;
  3062. // if image is wider or taller than the window height, adjust position to enable scrolling:
  3063. if ( ( ( $image_width - $(window).width() )/2 ) > 0 ) { $content_image.css({'left':0}); } else { $content_image.css({'left':'unset'}); }
  3064. if ( ( ( $image_height - $(window).height() )/2 ) > 0 ) { $content_image.css({'top':0}); } else { $content_image.css({'top':'unset'}); }
  3065. return;
  3066. }
  3067. if ( $content_pane.hasClass('has_grid') && ( $content_grid.hasClass('has_image_grid') || $content_grid.hasClass('has_grid') ) ) {
  3068. let $image_grid_item_width = Number.parseFloat( $('.image_grid_item img').width(),10) * incr;
  3069. let $image_grid_item_height = Number.parseFloat( $('.image_grid_item img').height(),10) * incr;
  3070. let $image_grid_item_maxwidth = Number.parseFloat( $('.image_grid_item img').css('maxWidth'),10) * incr;
  3071. let $image_grid_item_maxheight = Number.parseFloat( $('.image_grid_item img').css('maxHeight'),10) * incr;
  3072. // prevent reducing grid image size on first scale click:
  3073. if ( $image_grid_item_width < $image_grid_item_maxwidth ) { $image_grid_item_width = $image_grid_item_maxwidth; }
  3074. if ( $image_grid_item_height < $image_grid_item_maxheight ) { $image_grid_item_height = $image_grid_item_maxheight; }
  3075. // set grid properties
  3076. $content_grid.css({'grid-template-columns':'repeat(auto-fill, minmax('+ ($image_grid_item_width +16) +'px, auto ) )'});
  3077. $content_grid.find('img').css({'max-width':( $image_grid_item_width ) +'px', 'max-height':( $image_grid_item_height ) +'px'});
  3078. return;
  3079. }
  3080. }
  3081. // Scale Fonts and Images
  3082. function scalePreviewItems(y) { // combine scaling into one function
  3083. scaleImages( 1.125, y );
  3084. scaleFonts( 1.125, y );
  3085. }
  3086. // Scale Content
  3087. $('#scale').on('click','span',function(e) {
  3088. e.preventDefault();
  3089. e.stopPropagation();
  3090. let val = ( $(this).attr('id') === 'increase' ? 'increase' : 'decrease' );
  3091. scalePreviewItems(val);
  3092. });
  3093. // Zoom Images on click
  3094. function zoomImage(e) {
  3095. const $this_link = $content_image.attr('src');
  3096. const $offset = $content_image.offset();
  3097. const $this_width = $content_image.width();
  3098. const $this_height = $content_image.height();
  3099. const percentX = ( e.pageX - $offset.left ) / $this_width;
  3100. const percentY = ( e.pageY - $offset.top ) / $this_height;
  3101. const $content_container = $('#content_container');
  3102. const $CC_width = $content_container.width();
  3103. const $CC_height = $content_container.height();
  3104.  
  3105. if ( ( $content_image.attr('style') !== '' || $content_image.attr('style') !== undefined ) ) { $content_image.attr('style',''); }
  3106. if ( $this_link !== undefined ) {
  3107. getDimensions( $this_link, function( width, height ) {
  3108. if ( width < $CC_width && height < $CC_height ) { // don't zoom small images
  3109. $content_pane.removeClass('has_zoom_image').addClass('has_image'); // remove zoom classes in case window resized after zoom
  3110. } else if ( width > $CC_width && height < $CC_height ) { // scroll-x to click position, center vertically
  3111. $content_pane.toggleClass('has_image has_zoom_image');
  3112. if ( $content_pane.hasClass('has_image') ) { $content_image.css({'margin-top':0}); } else { $content_image.css({'margin-top': ( $CC_height - $content_image.height() )/2 }); }
  3113. $content_container.scrollLeft( width * percentX - ( $CC_width * percentX ) ) ;
  3114. } else if ( width < $CC_width && height > $CC_height ) { // center horizonally
  3115. $content_pane.toggleClass('has_image has_zoom_image');
  3116. if ( $content_pane.hasClass('has_image') ) { $content_image.css({'margin-left':0}); } else { $content_image.css({'margin-left': ( $CC_width - $content_image.width() )/2 }); }
  3117. $content_container.scrollTop( height * percentY - ( $CC_height * percentY ) );
  3118. } else {
  3119. $content_pane.toggleClass('has_image has_zoom_image');
  3120. $content_container.scrollLeft( width * percentX - ( $CC_width * percentX ) ) ;
  3121. $content_container.scrollTop( height * percentY - ( $CC_height * percentY ) );
  3122. }
  3123. });
  3124. }
  3125. }
  3126. $content_image.on('click', function(e) {
  3127. zoomImage(e);
  3128. });
  3129. // ***** END SCALE PREVIEW ITEMS ***** //
  3130.  
  3131.  
  3132. // ***** AUDIO CONTENT ***** //
  3133. // Update Playlist
  3134. function updatePlaylist() {
  3135. let playlist = [];
  3136. $audio_files.not('.unchecked').each(function() {
  3137. playlist.push( thisID( $(this) ) );
  3138. });
  3139. return playlist;
  3140. }
  3141. // Randomize Shuffle List
  3142. function shuffleArray(array) {
  3143. for ( let i = array.length - 1; i > 0; i-- ) {
  3144. const j = Math.floor(Math.random() * (i + 1));
  3145. [array[i], array[j]] = [array[j], array[i]];
  3146. }
  3147. return array;
  3148. }
  3149. // Attach Shuffle List data to $audio_player
  3150. function updateShuffleList(id) {
  3151. if ( !$body.hasClass('shuffle_audio') ) {
  3152. return;
  3153. } else if ( id !== undefined ) { // don't include .playing and .unchecked track in shufflelist
  3154. let shuffleList = $audio_player.data('shufflelist');
  3155. if ( $(document.getElementById(id)).hasClass('unchecked') || $(document.getElementById(id)).hasClass('playing') ) {
  3156. shuffleList.splice(shuffleList.indexOf(id), 1);
  3157. $audio_player.data('shufflelist',shuffleList);
  3158. } else {
  3159. shuffleList.push(id);
  3160. shuffleList = shuffleArray( shuffleList );
  3161. }
  3162. } else {
  3163. let shuffleList = shuffleArray( updatePlaylist() );
  3164. $audio_player.data('shufflelist',shuffleList);
  3165. }
  3166. }
  3167.  
  3168. // Check/Uncheck Audio/Video Files
  3169. function toggleChecked(e) {
  3170. e.stopPropagation();
  3171. $(this).blur();
  3172. thisRow(this).toggleClass('unchecked');
  3173. updateShuffleList(thisRow(this).attr('id'));
  3174. }
  3175. $media_files.on('click','input', toggleChecked );
  3176. // Check/Uncheck all Audio/Video Files
  3177. function toggleAllChecked(e) {
  3178. e.stopPropagation();
  3179. $dir_list_row.find('input').trigger('click');
  3180. updateShuffleList();
  3181. }
  3182. $dir_list.find('#play_toggle').on('click', toggleAllChecked );
  3183.  
  3184. // Is Playing
  3185. function isPlaying(el) {
  3186. return (el !== undefined && el.get(0).currentTime > 0 && !el.get(0).paused && !el.get(0).ended); // returns true if all conditions are true
  3187. }
  3188. // Play Media
  3189. function playMedia(task) {
  3190. if ( $playing_file().hasClass('audio') ) { $audio_player.trigger(task); } else { $content_video.trigger(task); }
  3191. }
  3192. // Skip media tracks +/-10/30 seconds
  3193. function mediaSkip(e) {
  3194. const factor = ( e.key === 'ArrowLeft' ? -1 : 1 ); // forward or backward?
  3195. const skip = ( e.altKey && e.shiftKey ? 30 : e.altKey ? 10 : null ); // 30s or 10s?
  3196. const $player = ( $playing_file().hasClass('audio') ? $audio_player : $content_video ); // audio or video?
  3197. const time = $player.prop('currentTime'); // current time
  3198. $player.prop('currentTime', time + factor*(skip)); // set time
  3199. }
  3200.  
  3201. // Play/Pause Audio/Video
  3202. function playPauseMedia() {
  3203. let $player = ( $content_pane.hasClass('has_audio') ? $('#audio') : $('#content_video') );
  3204. if ( isPlaying( $player ) ) { $player.trigger('pause'); } else { $player.trigger('play'); }
  3205. }
  3206. // Play Next Track
  3207. function playPrevNextTrackBtn(el) {
  3208. let key = ( el.attr('id') === 'prev_track' ? 'ArrowLeft' : 'ArrowRight' );
  3209. playPrevNextTrack(key);
  3210. }
  3211. // Prev/Next Track buttons
  3212. $('.prev_next_track_btn').on( 'click', function() { playPrevNextTrackBtn( $(this) ); });
  3213.  
  3214. // Toggle Shuffle Play
  3215. $('#checkbox_div').on('click','#shuffle', function() {
  3216. $body.toggleClass('shuffle_audio');
  3217. updateShuffleList();
  3218. if ( $body.hasClass('shuffle_audio') && $('.playing').length === 0 ) {
  3219. playPrevNextTrack('ArrowRight');
  3220. } else {
  3221. // do nothing: i.e., allow current track to continue playing
  3222. }
  3223. });
  3224. // Toggle Loop Play
  3225. $('#checkbox_div').on('click','#loop', function() {
  3226. $body.toggleClass('loop_audio');
  3227. document.getElementById('audio').toggleAttribute('loop');
  3228. });
  3229.  
  3230. // Initialize Audio
  3231. function initMedia() {
  3232. $('#audio, #content_video').on('ended', function() {
  3233. playPrevNextTrack('ArrowRight');
  3234. scrollThis('tbody','playing');
  3235. });
  3236. }
  3237. // ***** END AUDIO PLAYBACK ***** //
  3238.  
  3239. // ***** IFRAME SETUP ***** //
  3240. // For directory display or editable text files
  3241. // If row is a directory, set up iFrameDirUI(); if it's an editable text document, set up iFrameTextEditingUI().
  3242. function setUpIframeUI(bool) {
  3243. if ( window.self !== window.top ) {
  3244. const $textFiles = $row_types.markdown.concat($row_types.text, $row_types.code); // define which files are editable
  3245. // if selected index item is a directory, set up the directory UI....
  3246. if ( window.location.pathname.endsWith('/') ) {
  3247. iFrameDirUI( $('#iframe_body') );
  3248. }
  3249. if ( JSON.parse(bool) === true && $textFiles.includes( window.location.pathname.slice( window.location.pathname.lastIndexOf('.') + 1 ) ) ) {
  3250. $iFrame_head.append('<style>'+ $text_editing_style_rules +'</style>');
  3251. $iFrame_head.append('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>');
  3252. let splitClass = ( getQuery('split_view') === 'true' ? 'split_view' : '' );
  3253. let viewClass = getQuery('default_text_view');
  3254. let 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>';
  3255.  
  3256. TextEditing( '#iframe_body' );
  3257. $('#iframe_body').removeClass().addClass(splitClass).addClass(viewClass).prepend(warnings);
  3258. $('#content_source').removeClass().prop('disabled',false);
  3259. }
  3260. }
  3261. }
  3262. // IFRAME DIRECTORY Prep
  3263. function iFrameDirUI(el) { // el = iframeBody
  3264. let parentLink = decodeURIComponentSafe(window.location.pathname);
  3265. parentLink = parentLink.split('/').slice(0,-2).join('/') + window.location.search;
  3266. let queryPrefs = window.location.search;
  3267. queryPrefs = queryPrefs.slice(1).split('&');
  3268. queryPrefs = queryPrefs.map(x => x.split('='));
  3269. let prefs = Object.fromEntries(queryPrefs); // convert query array of arrays to object
  3270. let sortPref = prefs.sort_by; // sort determined by parent's current sorting pref
  3271. let numbersPref = prefs.show_numbers;
  3272. let viewSourcePref = prefs.view_source;
  3273. let iconsPref = prefs.use_custom_icons;
  3274. if ( viewSourcePref === 'true' ) {
  3275. return; // show raw directory index
  3276. } else {
  3277. if ( numbersPref === 'true' ) {
  3278. $('#iframe_body').addClass('show_numbers');
  3279. }
  3280. if ( iconsPref === 'true' ) {
  3281. $('#iframe_body').addClass('use_custom_icons');
  3282. }
  3283. $iFrame_head.find('style').remove();
  3284. $iFrame_head.append('<style>'+ $iframe_styles +'</style>');
  3285.  
  3286. const parentLinkCell = '<tr id="parent"><th colspan=4><a href="'+ parentLink +'">Parent Directory</a></th></tr>';
  3287. 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>';
  3288. const preppedIndex = makeNewIndex($('#iframe_body'),sortPref);
  3289. const iFrameTable = $('<table id="dir_list"><thead id="thead">'+ parentLinkCell + sortingRow +'</thead><tbody id="tbody"></tbody></table>');
  3290. // append prepped index
  3291. el.empty().append(iFrameTable).find('tbody').append(preppedIndex);
  3292. $('#iframe_body').find('#tbody').css({'top':$('#iframe_body').find('#thead').height() +'px'});
  3293. $('#iframe_body').data('sort_direction',1).find('#dir_list').addClass('sort_by_'+ sortPref); // initial directory sort
  3294. }
  3295. }
  3296.  
  3297. // IFRAME Directory sorting
  3298. $('#iframe_body').on('click','.sorting', function() {
  3299. const $dir_list_row = $('#iframe_body').find('#tbody').find('tr');
  3300. const id = $(this).attr('id');
  3301. if ( $('#iframe_body').data('sorting') !== id ) { // if clicking sorting item for the first time
  3302. $('#iframe_body').data('sorting',id);
  3303. $('#iframe_body').data('sorting',id).data('sort_direction', 1 );
  3304. $(this).removeClass('down');
  3305. } else { // clicking the same sorting item again -- reverse sort order
  3306. $('#iframe_body').data('sort_direction', $('#iframe_body').data('sort_direction') * -1 );
  3307. $(this).toggleClass('down');
  3308. }
  3309. const sort_direction = $('#iframe_body').data('sort_direction');
  3310. const $sorted_index = sortDirList( $dir_list_row, id, sort_direction );
  3311. $('#iframe_body').find('#dir_list').removeClass().addClass(id).find('#tbody').empty().append($sorted_index);
  3312. });
  3313. // ensure that iframe directory links include query string
  3314. $('#iframe_body').on('click','#dir_list tr.dir a', function(e) {
  3315. e.preventDefault();
  3316. let link = $(this).attr('href') + window.location.search;
  3317. window.location = link;
  3318. });
  3319. //***** TEXT EDITING PANE *****//
  3320. function textEditorUI() { //
  3321. if ( $content_text.children().length === 0 ) { // only add UI once
  3322. $body.addClass('has_text');
  3323. $content_pane.removeClass('has_dir');
  3324. if ( $content_pane.hasClass('has_grid') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  3325. TextEditing('#content_text');
  3326. $content_text.find('#content_preview').html($content_text.find('#content_source').val()); // make sure any source text is also previewed.
  3327. if ( getQuery('split_view') === 'true' || getQuery('default_text_view') === 'source_text' ) { $content_text.find('#content_source').focus(); }
  3328. } else { // show text editor
  3329. if ( !$body.hasClass('has_text') ) {
  3330. $body.addClass('has_text');
  3331. } else if ( $body.hasClass('has_text') || $body.hasClass('has_hidden_text') ) {
  3332. $body.toggleClass('has_text has_hidden_text');
  3333. }
  3334. if ( $content_pane.hasClass('has_grid') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  3335. if ( $content_pane.hasClass('has_hidden_grid ') ) { $content_pane.toggleClass('has_grid has_hidden_grid'); }
  3336. }
  3337. setContentTitle();
  3338. setContentHeight();
  3339. }
  3340. // show text editor pane
  3341. $('#text_editor, #text_editor_row').on('click', function(e) {
  3342. e.preventDefault();
  3343. textEditorUI();
  3344. });
  3345.  
  3346. // Main Text Editing Function
  3347. function TextEditing(id) { // container_el = $content_text or $content_iframe body
  3348. let container_el = $(id);
  3349. const $srctxt = ( container_el.find('> pre').length ? container_el.find('> pre').text() : container_el.html() ); // source text equals file content or nothing
  3350.  
  3351. MDbuildUI(id);
  3352.  
  3353. const $toolbar = container_el.find('#toolbar');
  3354. const $source = container_el.find('#content_source');
  3355. const $preview = container_el.find('#content_preview');
  3356. const $MDhandle = container_el.find('#text_editing_handle');
  3357.  
  3358. MDsetupTextEditingUI(id,$srctxt);
  3359. // Toolbar button functions
  3360. $toolbar.on('click','li,span',function(e) {
  3361. e.stopPropagation();
  3362. MDtoolBarFunctions($(this).attr('id'));
  3363. });
  3364. $(window).on('resize',function() {
  3365. $source.add($preview).add($MDhandle).attr('style','');
  3366. });
  3367. $('body#top').on('input', '#content_source', function() {
  3368. $source.add($preview).css({'height':$('#main_content').height() - $('#content_header').height() - 32 });
  3369. });
  3370.  
  3371. // Resize
  3372. $MDhandle.on('mousedown', function(e) {
  3373. e.stopPropagation();
  3374. MDresizeSplit($MDhandle,$source,$preview);
  3375. });
  3376. // Click labels to toggle checkboxes
  3377. $preview.add($toolbar).on('click','label', function(e) {
  3378. e.stopPropagation();
  3379. $(this).siblings('input').click();
  3380. });
  3381. // Sync scroll
  3382. $source.on('scroll',function() { MDsyncScroll(this); });
  3383. $preview.on('scroll',function() { MDsyncScroll(this); });
  3384.  
  3385. // TEXT EDITING
  3386. // Generate Preview
  3387. const $source_text = ( $source.length === 0 ? '' : $source.val() );
  3388. MDmarkdown( $source_text, $preview );
  3389.  
  3390. // Live preview update, and set edited classes for unsaved warning
  3391. $source.on('input', function() {
  3392. if ( !$('body').hasClass('edited') && $(this).parents('#top').length === 1 ) {
  3393. $('body#top').addClass('edited');
  3394. }
  3395. if ( !$('body').hasClass('edited') && $(this).parents('#iframe_body').length === 1 ) {
  3396. $('body#iframeBody').addClass('edited');
  3397. sendMessage('top','iframe_edited','','');
  3398. }
  3399. MDlivePreview($source,$preview);
  3400. });
  3401. // Checklists
  3402. MDsetChecklistClass();
  3403. // Live checkboxes
  3404. $preview.on('click','.checklist input',function(e) {
  3405. e.stopPropagation();
  3406. MDliveCheckBoxes($(this),$source,$preview);
  3407. });
  3408. // Preview TOC click navigation
  3409. $preview.on('click','.table-of-contents a',function(e) {
  3410. e.preventDefault();
  3411. MDtocClick($(this),$preview);
  3412. });
  3413. $preview.on('click','.uplink',function(e) {
  3414. e.stopPropagation();
  3415. MDheaderClick($preview);
  3416. });
  3417. }
  3418. ///// END MAIN MD FUNCTION
  3419.  
  3420. // MARKDOWN Functions
  3421. // MD Build UI
  3422. function MDbuildUI(id) {
  3423. const toggleSplitBtn = $('<li id="toggle_split" title="Toggle Split"></li>');
  3424. const syncScrollEl = $('<li id="sync_scroll"><input name="sync_scroll" type="checkbox"><label for="sync_scroll">Sync Scroll</label></li>');
  3425. const toggleSrcBtn = $('<li id="show_source" title="Show Source"></li>');
  3426. const togglePreviewBtn = $('<li id="show_preview" title="Show Preview"></li>');
  3427. const clearTextBtn = $('<li id="clear_text" title="Clear Text">Clear</li>');
  3428. 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>');
  3429. const buttonsCont = $('<ul id="toolbar"></ul>');
  3430. buttonsCont.append(toggleSrcBtn, togglePreviewBtn, toggleSplitBtn, syncScrollEl, saveBtn, clearTextBtn);
  3431. const textEditingUI = '<textarea id="content_source"></textarea><div id="content_preview" class="markdown-body"></div><div id="text_editing_handle"></div>';
  3432. // append the UI to the container_el
  3433. $(id).prepend(buttonsCont).append(textEditingUI);
  3434. }
  3435.  
  3436. // MD Set up UI
  3437. function MDsetupTextEditingUI(id,sourceText) {
  3438. $(id).find('pre').first().remove();
  3439. $(id).find('#content_source').val(sourceText); // set source text from pre
  3440. if ( getQuery('split_view') === 'true' ) { $('body').addClass('split_view'); } else { $('body').removeClass('split_view'); }
  3441. if ( getQuery('default_text_view') === 'preview' ) { $('body').addClass('preview_text').removeClass('source_text'); } else { $('body').addClass('source_text').removeClass('preview_text'); }
  3442. if ( getQuery('sync_scroll') === 'true' ) { $('#sync_scroll input').prop({checked:true}); }
  3443. }
  3444. // MD UI Buttons functions
  3445. function MDtoolBarFunctions(id) {
  3446. let $thisFileName;
  3447. let container_el = $(getElById(id)).closest('body');
  3448. let sourceEl = container_el.find('#content_source');
  3449. let previewEl = container_el.find('#content_preview');
  3450. if ( $body.hasClass('has_text') ) {
  3451. $thisFileName = 'untitled';
  3452. } else {
  3453. $thisFileName = decodeURI(window.location.pathname.slice(window.location.pathname.lastIndexOf('/') + 1));
  3454. }
  3455. 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">';
  3456. const $saveHTMLClose = '</body></html>';
  3457. switch (id) {
  3458. case 'toggle_split':
  3459. $('body').toggleClass('split_view').find('#content_source,#content_preview,#text_editing_handle').attr('style','');
  3460. if ( container_el.hasClass('source_text') ) {
  3461. sourceEl.focus();
  3462. document.getElementById('content_source').setSelectionRange(0,0);
  3463. }
  3464. break;
  3465. case 'show_source':
  3466. 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
  3467. sourceEl.css({'width':'100%'}).focus();
  3468. document.getElementById('content_source').setSelectionRange(0,0);
  3469. break;
  3470. case 'show_preview':
  3471. container_el.removeClass('split_view source_text').addClass('preview_text').find('#content_source,#content_preview,#text_editing_handle').attr('style','');
  3472. break;
  3473. case 'clear_text':
  3474. container_el.addClass('has_warning').find('#warnings').removeClass().addClass('clear');
  3475. break;
  3476. case 'save_text':
  3477. saveMD( $thisFileName, sourceEl.val() );
  3478. break;
  3479. case 'save_HTML':
  3480. saveMD( $thisFileName.slice(0,$thisFileName.lastIndexOf('.') + 1) + 'html', $saveHTMLOpen + MDprepHTML(previewEl.html()) + $saveHTMLClose );
  3481. break;
  3482. }
  3483. }
  3484. // MD Custom pre- and post-processing for text.
  3485. function MDaddHeaderIDs(match, p1, p2, p3, offset, string) { // create header ids for TOC
  3486. return '<h'+ p1 +' id="'+ p3.toLowerCase().replace(/\s/g,'-') +'" ' + p2 +'>'+ p3;
  3487. }
  3488. function MDcustomPreProcess(src) {
  3489. return src; // we're not doing anything here just yet...
  3490. }
  3491. function MDcustomPostProcess(html) {
  3492. 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
  3493. .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
  3494. // .replace(/<li><p class="checklist">"/g,'<li class="checklist"><p>')
  3495. .replace(/^<h(\d)([^>]*)>([^<]+)/gm, MDaddHeaderIDs) // add header IDs;
  3496. .replace(/<\/h(\d)>/g,'<span class="uplink">&uarr;</span></h$1>');
  3497. return html;
  3498. }
  3499. //MD Render markdown from preprocessed source text
  3500. function MDmarkdown(sourceText,previewEl) {
  3501. const MDit = window.markdownit({linkify:false,typography:false,html:true})
  3502. .use(window.markdownitMultimdTable, {enableMultilineRows: true})
  3503. .use(window.markdownitSub)
  3504. .use(window.markdownitSup)
  3505. .use(window.markdownitFootnote)
  3506. .use(window.markdownitCentertext)
  3507. .use(window.markdownitDeflist)
  3508. .use(window.markdownitTocDoneRight)
  3509. ;
  3510. let MDpreview = MDit.render( MDcustomPreProcess( sourceText ) );
  3511. previewEl.html( MDcustomPostProcess( MDpreview ) ); // set previewed html
  3512. }
  3513. // MD Live preview, add edited warning
  3514. function MDlivePreview(sourceEl,previewEl) {
  3515. MDmarkdown( sourceEl.val(),previewEl );
  3516. MDsetChecklistClass();
  3517. }
  3518.  
  3519. // MD Live Checkboxes prep: find each instance of [ ] or [x] and replace text in index = to clicked checkbox in Preview.
  3520. function MDreplaceAt(str, replacement, position) {
  3521. str = str.substring(0, position) + replacement + str.substring(position + replacement.length);
  3522. return str;
  3523. }
  3524. function MDreplaceNthSubStr(str,substr,replacement,index) {
  3525. let count = 0;
  3526. let found = substr.exec(str);
  3527. while ( found !== null ) {
  3528. if ( count === index ) {
  3529. return MDreplaceAt(str, replacement, found.index );
  3530. } else {
  3531. count++;
  3532. found = substr.exec(str);
  3533. }
  3534. }
  3535. }
  3536. // MD Live Checkboxes
  3537. function MDliveCheckBoxes(checkbox,sourceEl,previewEl) {
  3538. $('.checklist').removeClass('clicked');
  3539. checkbox.closest('p,li,dt,dd').addClass('clicked');
  3540. const thisIndex = previewEl.find('.checklist').index( $('.clicked') );
  3541. const srctext = sourceEl.val();
  3542. const substr = new RegExp(/\[\s*.\s*\]/g);
  3543. const replacement = ( checkbox.is(':checked') ? '[x]' : '[ ]' );
  3544. sourceEl.val( MDreplaceNthSubStr(srctext, substr, replacement, thisIndex) );
  3545. }
  3546. // MD Checkbox list class: Prevent checkbox lists from having list bullets
  3547. function MDsetChecklistClass() {
  3548. $('input[type="checkbox"]').closest('ul').addClass('no_list');
  3549. }
  3550.  
  3551. // MD Resize Split View
  3552. function MDresizeSplit(handle,sourceEl,previewEl) {
  3553. let $sidebarWidth = $('#sidebar').outerWidth();
  3554. let $pageWidth = window.innerWidth;
  3555.  
  3556. $(document).on('mousemove',function(e) {
  3557. e.stopPropagation();
  3558. e.preventDefault();
  3559. let pageX = e.pageX;
  3560. if ( pageX > $sidebarWidth + 100 && pageX < $pageWidth - 100 ) { // min widths
  3561. handle.css({'left': pageX - $sidebarWidth - 4 + 'px'});
  3562. sourceEl.css({'width': pageX - $sidebarWidth + 'px'});
  3563. previewEl.css({'left': sourceEl.outerWidth() + 'px'});
  3564. }
  3565. });
  3566. handle.on('mouseup',function() {
  3567. $(document).off('mousemove');
  3568. });
  3569. }
  3570. // MD UI Sync Scroll
  3571. function MDpercentage(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)); }
  3572. function MDsyncScroll(el1) {
  3573. let el2 = ( el1.getAttribute('id') === 'content_preview' ? document.getElementById('content_source') : document.getElementById('content_preview') );
  3574. if ( document.querySelector('input[name="sync_scroll"').checked ) {
  3575. el2.scrollTo( 0, (MDpercentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
  3576. }
  3577. }
  3578. // click TOC anchors
  3579. function MDtocClick(el,previewEl) {
  3580. let thisId = el.attr('href');
  3581. if ( thisId ) {
  3582. previewEl.scrollTop( $(thisId).offset().top - 48 );
  3583. }
  3584. }
  3585. // click Headers to return to TOC or top
  3586. function MDheaderClick(previewEl) {
  3587. if ( previewEl.find('.table-of-contents').length > 0 ) {
  3588. document.getElementsByClassName('table-of-contents')[0].scrollIntoView(true);
  3589. } else {
  3590. document.getElementById('preview').scroll(0,0);
  3591. }
  3592. }
  3593. // MD Clear text source
  3594. function clearText(container_el) {
  3595. if ( window.top !== window.self ) { // if iframe, send message to top (to remove iframe_edited class)
  3596. sendMessage('top','clear');
  3597. }
  3598. container_el.find('#content_source').show().focus().val('');
  3599. container_el.find('#content_preview').empty();
  3600. container_el.removeClass('edited has_warning');
  3601. }
  3602. // MD SAVE SOURCE or HTML
  3603. function MDprepHTML(data) {
  3604. data = data.replace(/<span\sclass="uplink">.<\/span>/g,'');
  3605. return data;
  3606. }
  3607. function saveMD(filename, data) {
  3608. let blob = new Blob([data], {type: 'text/plain'});
  3609. let downloadEl = window.document.createElement('a');
  3610. downloadEl.href = window.URL.createObjectURL(blob);
  3611. downloadEl.download = filename;
  3612. document.body.appendChild(downloadEl);
  3613. downloadEl.click();
  3614. document.body.removeChild(downloadEl);
  3615. URL.revokeObjectURL(blob);
  3616. if ( window.top !== window.self ) { // if iframe, send message to top
  3617. sendMessage('top','clear');
  3618. }
  3619. $('body,#content_source,#content_text').removeClass('edited');
  3620. }
  3621. // list of functions to remember while sending messages and then execute after warning button click
  3622. function doFunction(funcName,args) {
  3623. var funcDictionary = { 'setLocation':setLocation, 'resetContent':resetContent, 'closeContent':closeContent, 'clickThis':clickThis, 'clickRow':clickRow, 'doubleClickRow':doubleClickRow, 'indexNavigation':indexNavigation, 'clearText':clearText, 'null':null };
  3624. return funcName === 'null' ? null : funcDictionary[funcName](args);
  3625. }
  3626. // Show warning after certain user actions if text editor or iframe has edited text; otherwise do the action.
  3627. function showWarning(funcName,args) {
  3628. // Don't show the warning if func = indexNavigation or clickRow; i.e., just hide text editor;
  3629. // In other words, only show warning when changing directories or if iframe content has been edited
  3630. if ( ( $('body').hasClass('edited') && funcName !== 'indexNavigation' && funcName !== 'clickRow' ) || $('body').hasClass('iframe_edited') ) {
  3631. if ( $('body').hasClass('edited') ) { // show warning and text editor (if hidden)
  3632. $body.addClass('has_warning').find('#warnings').removeClass().addClass('unloading');
  3633. $body.removeClass('has_hidden_text').addClass('has_text');
  3634. }
  3635. if ( $('body').hasClass('iframe_edited') ) { // if iframe is edited, send unloading message
  3636. sendMessage('iframe','unloading',funcName,args); // upon receipt of message, iframe will show its warning message, based on the funcName
  3637. }
  3638. } else {
  3639. doFunction(funcName,args);
  3640. }
  3641. }
  3642. // Send a message to iframe or parent
  3643. function sendMessage(target,message,funcName,args) {
  3644. var messageObj = { 'messageContent': message, 'functionName': funcName, 'arguments': args };
  3645. if ( target === 'iframe' ) {
  3646. let contentIFrame = document.getElementById('content_iframe');
  3647. contentIFrame.contentWindow.postMessage( messageObj, '*' );
  3648. }
  3649. if ( target === 'top' ) {
  3650. window.parent.postMessage( messageObj, '*');
  3651. }
  3652. }
  3653. // Receive a message from iframe or parent, do appropriate action
  3654. function receiveMessage(e) {
  3655. if ( e.origin === 'null' || e.origin === $origin ) {
  3656. let $message = e.data.messageContent;
  3657. let funcName = e.data.functionName;
  3658. let args = e.data.arguments;
  3659.  
  3660. if ( $message === 'split_view' ) {
  3661. $iframe_body.toggleClass('split_view');
  3662. }
  3663. if ( $message === 'default_text_view' ) {
  3664. $iframe_body.toggleClass('preview_text source_text').removeClass('split_view');
  3665. }
  3666. // warn iframe that user wants to change iframes
  3667. if ( $message === 'unloading' && !$iframe_body.hasClass('has_warning') ) {
  3668. $iframe_body.addClass('has_warning').find('#warnings').removeClass().addClass('unloading').attr('data-function_name',funcName).attr('data-args',args);
  3669. }
  3670. // let top know iframe text has been edited
  3671. if ( $message === 'iframe_edited' && !$('body#top').hasClass('iframe_edited') ) {
  3672. $('body#top').addClass('iframe_edited');
  3673. }
  3674. if ( $message === 'ignore' || $message === 'clear' ) {
  3675. $('body#top').removeClass('iframe_edited');
  3676. if ( $message === 'ignore' ) { doFunction(funcName,args); }
  3677. }
  3678. }
  3679. }
  3680. window.addEventListener('message',receiveMessage,false);
  3681.  
  3682. // Edited Warning buttons: what to do when the user clicks a warning button
  3683. function editedWarningButtons(id) {
  3684. let btn = $(document.getElementById(id));
  3685. let container_el = btn.closest('body');
  3686. let func = $('#warnings').attr('data-function_name');
  3687. let args = $('#warnings').attr('data-args');
  3688. switch(id) {
  3689. case 'warning_ignore_btn': // do the user initiated func without saving the edited text
  3690. if ( window.self !== window.top ) { // if iframe, send message to top
  3691. sendMessage('top','ignore',func,args);
  3692. }
  3693. container_el.removeClass('edited has_text has_warning');
  3694. clearText(container_el);
  3695. break;
  3696. case 'warning_cancel_btn': // cancel the func
  3697. container_el.removeClass('has_warning').find('#warnings').removeClass();
  3698. break;
  3699. case 'warning_clear_btn': // clear the text editor
  3700. clearText(container_el);
  3701. break;
  3702. case 'warning_save_btn': // save the text
  3703. if ( window.top !== window.self ) { // if iframe, send message to top
  3704. sendMessage('top','clear');
  3705. }
  3706. container_el.removeClass('edited has_warning');
  3707. $('#save_text').click();
  3708. break;
  3709. case 'warning_ok_btn': // clear the text editor
  3710. $('body').removeClass('has_warning').find('#warnings').removeClass('local');
  3711. break;
  3712. }
  3713. }
  3714. $('#warnings').on('click','button',function(e) {
  3715. e.preventDefault();
  3716. editedWarningButtons( $(this).attr('id') );
  3717. });
  3718. // Edited Warning overlay: prevent user clicks on rest of UI
  3719. $('#overlay').on('click mousedown mouseup',function(e) {
  3720. e.preventDefault();
  3721. e.stopPropagation();
  3722. return;
  3723. });
  3724.  
  3725. // END Text Editing
  3726.  
  3727. // EXPERIMENTAL: Open playlist
  3728. $('#open_playlist_label').on('click',function(e) { $('.menu').hide(); });
  3729. // Open font
  3730. $('#bookmarks').on('change','#open_playlist',function(e) {
  3731. openPlaylist(e);
  3732. });
  3733. function convertPlaylist(items) {
  3734. let preppedIndex = '';
  3735. let preppedRow = '';
  3736. let id = 0, rows, info, title, time = '0', display_time = '—', link, kind = '', display_kind, ext;
  3737. items = items.replace(/\s*#EXTM3U.*\n/,'\n').replace(/^\*\n{2,}/gm,'\n');
  3738. if ( items.indexOf('#EXTINF:') !== -1 ) {
  3739. rows = items.split('#EXTINF:');
  3740. } else {
  3741. rows = items.split('\n');
  3742. }
  3743. for ( let row of rows ) {
  3744. if ( row.indexOf('\n') !== -1 && row.trim().length > 0 ) {
  3745. row = row.trim().split('\n');
  3746. info = row[0];
  3747. time = info.slice(0,info.indexOf(','));
  3748. display_time = new Date(time * 1000).toISOString().substr(11, 8);
  3749. title = info.slice(info.indexOf(',') + 1);
  3750. link = row[1];
  3751. } else {
  3752. title = decodeURIComponentSafe(row.slice(row.lastIndexOf('/') + 1));
  3753. link = row;
  3754. }
  3755. ext = link.slice(link.lastIndexOf('.') + 1);
  3756.  
  3757. if ( $row_types.audio.includes( ext ) ) {
  3758. kind = 'audio';
  3759. display_kind = 'Audio';
  3760. } else if ( $row_types.video.includes( ext ) ) {
  3761. kind = 'video';
  3762. display_kind = 'Video';
  3763. }
  3764. if ( kind !== '' ) { // only allow supported media types
  3765. preppedRow = '<tr id="rowid-'+ id +'" class="file media '+ kind +'" data-ext="'+ ext +'"><td class="name" data-name="'+ title +'"><a class="icon" href="'+ link +'"><span><input type="checkbox" tabindex="-1" checked="true">'+ title +'</span></a></td><td class="size details" data-size="'+ time +'">'+ display_time +'</td><td class="date details" data-date="0">—</td><td class="kind details" data-kind="'+ kind +'">'+ display_kind +'</td><td class="ext details" data-ext="'+ ext +'"></td></tr>';
  3766. preppedIndex += preppedRow;
  3767. }
  3768. id++;
  3769. }
  3770. closeContent();
  3771. closeOtherContent();
  3772. $dir_list.find('tbody').empty().addClass('playlist').append(preppedIndex);
  3773. $dir_list.find('#stats').html(getIndexStats($dir_list.find('#tbody tr')));
  3774. $body.addClass('has_playlist has_audio');
  3775. setContentTitle();
  3776. setContentHeight();
  3777. }
  3778. function openPlaylist(evt) {
  3779. if (window.File && window.FileReader && window.FileList && window.Blob) {
  3780. var files = evt.target.files;
  3781. var reader = new FileReader();
  3782. reader.onload = function(file) {
  3783. convertPlaylist(file.target.result);
  3784. return true;
  3785. };
  3786. reader.readAsText(files[0]);
  3787. $('#parents_dir_menu').find('> div').empty().html('Playlist: '+ files[0].name );
  3788. $('#open_playlist').val('');
  3789. } else {
  3790. alert('File APIs are not fully supported in this browser.');
  3791. }
  3792. }
  3793.  
  3794. })();
  3795. // THE END!