Supercharged Local Directory File Browser

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

当前为 2020-06-04 提交的版本,查看 最新版本

  1. /* eslint-disable no-mixed-spaces-and-tabs */
  2. /* eslint-disable no-useless-escape */
  3. /* eslint-disable no-fallthrough */
  4. /* eslint-disable indent */
  5. // ==UserScript==
  6. // @name Supercharged Local Directory File Browser
  7. // @version 5.0.2
  8. // @description Makes file:/// directory ("Index of...") pages (and many server-generated index pages) actually useful. Adds sidebar and preview pane; keyboard navigation and sorting; media playback with shuffle, loop, and playlist (m3u) support; preview, edit, and save markdown/plain text files; preview images and fonts; grid views for images and fonts; user-defined bookmarks; more.
  9. // @author gaspar_schot
  10. // @license GPL-3.0-or-later
  11. // @homepageURL https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser
  12. // @contributionURLhttps://paypal.me/mschrauzer
  13. // @include file://*
  14. // @include about:blank
  15.  
  16. // @require https://code.jquery.com/jquery-latest.min.js
  17.  
  18. // @require https://cdn.jsdelivr.net/npm/markdown-it@11.0.0/dist/markdown-it.min.js
  19. // @require https://cdn.jsdelivr.net/npm/markdown-it-footnote@3.0.2/dist/markdown-it-footnote.min.js
  20. // @require https://cdn.jsdelivr.net/npm/markdown-it-toc-done-right@2.1.0/dist/markdown-it-toc-made-right.min.js
  21. // @require https://cdn.jsdelivr.net/npm/markdown-it-sub@1.0.0/dist/markdown-it-sub.min.js
  22. // @require https://cdn.jsdelivr.net/npm/markdown-it-sup@1.0.0/dist/markdown-it-sup.min.js
  23. // @require https://cdn.jsdelivr.net/npm/markdown-it-deflist@2.0.3/dist/markdown-it-deflist.min.js
  24. // @require https://cdn.jsdelivr.net/npm/markdown-it-multimd-table@4.0.2/dist/markdown-it-multimd-table.min.js
  25. // @require https://cdn.jsdelivr.net/npm/markdown-it-center-text@1.0.4/dist/markdown-it-center-text.min.js
  26.  
  27. // @require https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js
  28.  
  29. // UPDATE URL
  30.  
  31. // NOTE: This script was developed in Vivaldi, running on Mac OS Mojave. It has been tested in various Chrome and Gecko-based browsers.
  32. // It has been minimally tested on Windows and not at all on other OSes. It should work, but please report any issues.
  33. // The script does not work on local directories in Safari because Safari does not allow local directories to be browsed, but it will work on remote directories (or on local directories through a local server).
  34.  
  35. // 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.
  36. // For Tampermonkey, go to Chrome extension page, and tick the 'Allow access to file URLs' checkbox at the Tampermonkey extension section.
  37. // For Greasemonkey, open about:config and change greasemonkey.fileIsGreaseable to true.
  38.  
  39. // @namespace https://greasyfork.org/users/16170
  40. // ==/UserScript==
  41.  
  42. (function() {
  43. 'use strict';
  44. const $ = window.jQuery;
  45.  
  46. // ***** USER SETTINGS ***** //
  47. const $settings = {
  48. // NOTE: These settings will be overwritten whenever the script is updated. Use the "Export User Settings" menu item to save them.
  49. // You can paste the exported settings between the two lines below:
  50. //--------------------------------------------------------//
  51.  
  52. bookmarks: // N.B.: Directory links must end with "/", file links must end with another character.
  53. // You may add as many menus and links as you like; just copy the example below and edit as needed.
  54. // Local directory bookmarks must begin with "file:///"; external bookmarks must begin with the correct protocol ("http://" or "ftp://", etc.).
  55. [
  56. // { 'menu_title':'My Sample Menu',
  57. // 'links': [
  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. { 'menu_title':'My Sample Menu',
  64. 'links': [
  65. { 'link_name':'My Directory Link 1', 'link':'file:///Path/To/My/Directory/' },
  66. { 'link_name':'My Directory Link 2', 'link':'file:///Path/To/My/Directory_2/' },
  67. { 'link_name':'My External Link', 'link':'https://www.mywebpage.com/' },
  68. { 'link_name':'My File Link', 'link':'file:///Path/To/My/File.ext' },
  69. ]},
  70. ],
  71. // GENERAL USER SETTINGS
  72. alternate_background: true, // If true (default true), alternate sidebar row background color.
  73. apps_as_dirs: false, // Un*x/Mac OS only: if true, treat apps as directories; allows app contents to be browsed. This is the default behavior for Chrome.
  74. // If false (default), treat apps as ignored files.
  75. autoload_media: true, // If true (default), the first audio or video file found in a directory will be automatically selected and loaded for playback.
  76. // Also, cover art files (if any) will be loaded in the preview pane.
  77. // Files with 'cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes' in the title will be loaded first in that order, with exact matches having preference.
  78. // Note that there can be false positives because a file will be matched whenever there is no exact match and one of these words appears in an image name.
  79. // Otherwise the first image file in directory will be loaded.
  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. editor_theme: 'light', // Options: if not set (default), uses "theme" setting; otherwise 'light' or 'dark'.
  84. sort_by: 'default', // Choose from: 'name', 'size', 'date', 'kind', 'ext', 'default'.
  85. // default = Chrome sorting: dirs on top, files alphabetical.
  86. 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").
  87. // 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.)
  88. grid_font_size: 1, // Default = 1
  89. grid_image_size: 184, // Default = 184 (200px - 16px)
  90. show_details: true, // If true (default), hide file and directory details; if false, show them.
  91. hide_ignored_items: false, // If true, ignored files (= files the browser cannot natively open, e.g., common office and graphics files) will be hidden.
  92. // If false (default), ignored files will appear greyed-out.
  93. ignore_ignored_items: true, // If true (default), clicking ignored file types (see $row_settings below) in the directory list will not cause the browser to attempt to open the file.
  94. // It is recommended to leave the default unchanged.
  95. // Ignored items can still be downloaded with ctrl-click or right-click.
  96. show_invisibles: true, // Un*x/Mac OS only: If true (default), files or directories beginning with a "." will be hidden.
  97. show_numbers: true, // If true (default true), number index items
  98. UI_font: 'system-ui, sans-serif', // Choose an installed font for the UI; if undefined, use browser defaults instead.
  99. UI_font_size: '13px', // Choose a default UI font size; use any standard CSS units.
  100. use_custom_icons: true, // if true (default), use custom icons for dirs and files
  101. // if false, use browser/server default icons
  102. // TEXT EDITING SETTINGS
  103. disable_text_editing: false, // If false (default), allow plain text files to be edited.
  104. default_text_view: 'preview_text', // Options: 'source_text' or 'preview_text' for text editor.
  105. // Note that split_view = true overrides this setting.
  106. split_view: true, // If true, show split view on plain text file load.
  107. // if true (default), use default preview_text setting.
  108. sync_scroll: true // If true (default: true), show split view on plain text file load
  109. // if false, use default preview_text setting.
  110. //--------------------------------------------------------//
  111. // Paste your exported settings between the above two lines.
  112. };
  113.  
  114. // $ITEM_KIND:
  115. // DO NOT DELETE ANY EXISTING CATEGORIES!
  116. // Add file extensions for sorting and custom icon display to the existing categories.
  117. // You can also define your own new categories, but do not add an extension to more than one row_type category.
  118. // Do not add leading "." to the extensions.
  119. const $item_kind = {
  120. // myRowType: ['ext1','ext2'],
  121. dir: ['/'], // loaded in iframe#content_iframe
  122. app: ['app/','app','bat','cgi','com','exe','jar','msi','wsf'], // generally ignored; apps may be opened as directories
  123. alias: ['alias','desktop','directory','lnk','symlink','symlink/'],
  124. archive: ['7z','archive','b6z','bin','bzip','bz2','cbr','dmg','gz','iso','mpkg','pkg','rar','sit','sitx','tar','tar.gz','zip','zipx','zxp'], // ignored
  125. audio: ['aac','aif','aiff','ape','flac','m4a','mp3','ogg','opus','wav'], // loaded in audio#audio
  126. bin: ['a','bundle','dll','dyld','dylib','gem','icc','msi','pyc','pyo','o','rakefile','ri','so','xml','2'], // ignored
  127. code: ['bak','bash','bash_profile','bashrc','c','cfg','cnf','codes','coffee','conf','csh','cshrc','cson','css','custom_aliases','d','default','dist','editorconfig','emacs','example','gemspec','gitconfig','gitignore','gitignore_global','h','hd','ini','js','json','jsx','less','list','local','login','logout','lua','mkshrc','old','pc','php','pl','plist','pre-oh-my-zsh','profile','pth','py','rb','rc','rdoc','sass','settings','sh','strings','taskrc','tcl','viminfo','vimrc','vue','yaml','yml','zlogin','zlogout','zpreztorc','zprofile','zsh','zshenv','zshrc'], // treated as text, opened in iframe#content_iframe text editor
  128. database: ['accdb','db','dbf','mdb','pdb','sql', 'sqlite','sqlitedb','sqlite3'], // ignored
  129. ebook: ['azw','azw1','azw3','azw4','epub','ibook','kfx','mobi','tpz'], // ignored
  130. font: ['otf','ttf','woff','woff2','afm','pfb','pfm','tfm'], // opened in div#content_font
  131. graphics: ['afdesign','afpub','ai','book','dtp','eps','fm','icml','idml','indd','indt','inx','mif','pmd','pub','qxb','qxd','qxp','sla','swf'], // ignored
  132. htm: ['htm','html','xhtm','xhtml'], // opened in iframe#content_iframe
  133. image: ['apng','bmp','gif','ico','jpeg','jpg','png','svg','webp'],
  134. ignored_image: ['ai','arw','cr2','dng','eps','jpf','nef','psd','psd','raw','tif','tiff'], // ignored
  135. link: ['url','webloc','inetloc'],
  136. markdown: ['md','markdown','mdown','mkdn','mkd','mdwn','mdtxt','mdtext'], // treated as text, opened in iframe#content_iframe text editor
  137. office: ['csv','doc','docx','epub','key','numbers','odf','ods','odt','pages','rtf','scriv','wpd','wps','xlr','xls','xlsx','xlm'], // ignored
  138. pdf: ['pdf'], // open in embed#content_pdf
  139. system: ['DS_Store','ds_store','icon','ics','spotlight-v100/','temporaryitems/','documentrevisions-v100/','trashes/','fseventsd/','dbfseventsd','file','localized','programdata'], // ignored system items
  140. text: ['log','nfo','txt'], // opened in iframe#content_iframe text editor
  141. video: ['m4v','mov','mp4','mpeg','webm'] // loaded in video#content_video
  142. };
  143. // $ROW_SETTINGS: Ignore or Exclude files by extension
  144. const $row_settings = {
  145. // ignored: $item_kind or files with extensions added here will not be loaded if selected in the sidebar (prevents the browser from attempting to download the file).
  146. ignored: $item_kind.archive.concat( 'alias', $item_kind.bin, $item_kind.database, $item_kind.graphics, $item_kind.ignored_image, $item_kind.office, $item_kind.system),
  147. // Exclude: Files with these exensions will not be inverted in dark mode
  148. exclude: ['htm','html','xhtm','xhtml']
  149. };
  150. // ***** END USER SETTINGS ***** //
  151.  
  152. // ## FEATURES INCLUDE:
  153. // - Resizable sidebar and directory/file preview pane.
  154. // - Arrow navigation in sidebar:
  155. // - Up and Down Arrows select next/prev item.
  156. // - Left and Right Arrows select next/prev item of same type.
  157. // - Navigate sidebar by typed string.
  158. // - Show/Hide file details (size (if avail), date modified (if avail), kind, extension).
  159. // - Sort sidebar items by name or file details.
  160. // - Default sort = sort by name with folders on top.
  161. // - Preview all file types supported natively by browser (html, text, images, pdf, audio, video, etc.) and preview fonts.
  162. // - Preview and edit markdown (if your browser supports it) and plain text files, with option to save files locally.
  163. // - Markdown rendered with markdownit.js ( https://github.com/markdown-it/markdown-it ).
  164. // - Uses Github Markdown styles for preview ( https://github.com/sindresorhus/github-markdown-css ), with a few customizations.
  165. // - Support for:
  166. // - TOC creation ( `${toc}` ) ( https://github.com/nagaozen/markdown-it-toc-done-right )
  167. // - Multimarkdown table syntax ( https://github.com/RedBug312/markdown-it-multimd-table )
  168. // - Live checkboxes ( `\[ ], [x]` ), allowed in lists and deflists.
  169. // - Superscript ( `^sup^` ) ( https://github.com/markdown-it/markdown-it-sup )
  170. // - Subscript ( `~sub~` ) ( https://github.com/markdown-it/markdown-it-sub )
  171. // - Definition lists ( https://github.com/markdown-it/markdown-it-deflist; for syntax, see http://pandoc.org/MANUAL.html#definition-lists )
  172. // - Centered text ( `->centered<-` ) ( https://github.com/jay-hodgson/markdown-it-center-text )
  173. // - Footnotes ( https://github.com/markdown-it/markdown-it-footnote )
  174. // - View source text, preview, or split pane with proportional sync scroll.
  175. // - Save edited source text or previewed HTML.
  176. // - Create and edit text in separate text editor.
  177. // - Audio and video playback, with shuffle, loop, skip audio +/- 10 or 30 sec via keyboard.
  178. // - Preview other files (e.g., lyrics or cover art) in same directory while playing audio.
  179. // - User setting to autoload cover art (if any images in directory, load "cover.ext" or first image found)
  180. // - Grid views for images and fonts.
  181. // - Playlists and filelists.
  182. // - Open standard .m3u media files; save .m3u file of all media items in current directory.
  183. // - Open and save custom .m3u files that may contain directories and _any_ file type.
  184. // - Click file or audio title to display .m3u listing for that item.
  185. // - 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):
  186. // - Light or Dark theme.
  187. // - Bookmarks for local or remote directories.
  188. // - Default image grid size.
  189. // - Default UI font size and font-family.
  190. // - Default UI font and font-size.
  191. // - Default file sorting.
  192. // - Sort with directories on top.
  193. // - Treat apps as directories (MacOS and *nix only)
  194. // - Show or hide invisible files.
  195. // - Show or hide ignored files in the ignored files list (see $row_settings in code below $settings).
  196. // - Show or hide file details.
  197. // - Use custom file icons or browser defaults.
  198. // - Autoload index.ext files.
  199. // - Autoload cover art in directories with audio files.
  200. // - Text editing default view: split, source, or preview.
  201. // - Text editing sync scroll: on or off.
  202.  
  203. // CHANGELOG:
  204.  
  205. // **VERSION 5.0.2:** Lots of bug fixes.
  206. // **ADDED:** Experimental support for opening .webloc and .url files: select file in sidebar and use double-click, Cmd/Ctr-Return, or Cmd/Ctr-ArrowDown to load the link.
  207. // Note: This is untested on Windows; please report any problems.
  208. // Note: Behavior for link files located on remote servers can be unpredictable, depending on server configuration.
  209. // Specifically, link files with .com, .org, or .net in their names (e.g., www.example.com.webloc) may not work.
  210. // **FIXED & IMPROVED:** Completely overhauled handling of invisible and ignored files and directories.
  211. // - (Ignored files are those which the browser typically cannot open natively, like common office or graphics files.)
  212. // - Added a new "Ignored Items" menu item with "Hide Ignored Items" and "Ignore Ignored Items" items.
  213. // - By default, ignored items are visible.
  214. // - If "Ignored Ignored Items" is checked (default), ignored items can be selected in the sidebar, but will not be downloaded. Uncheck it in order to restore the default browser behavior. It is recommended to leave the default setting unchanged.
  215. // - Visibility of items that are both invisible and ignored can be toggled with the "Show Invisibles" checkbox.
  216. // **FIXED:** Several problems with tabbing into previewed text documents.
  217. // **FIXED:** An issue with arrow key behaviour when the text editor was open.
  218. // **FIXED:** Various issues with keydown events in textareas and contenteditable elements.
  219. // **FIXED:** Main menu: toggling default text editor view wasn't working, and default settings weren't being honored; fixed some styling issues.
  220. // **FIXED:** Help window wasn't styled correctly.
  221. // **FIXED:** Long-standing issues with autoloading cover art. Many times the incorrect file was loaded; selection should be much more robust now.
  222. // - Image files with 'cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes' in the title will be loaded first in that order, with exact matches having preference;
  223. // - Otherwise the first image found will be loaded.
  224. // - Note that there can be false positives because a file will be matched whenever there is no exact match and one of these words appears in an image name.
  225. // **IMPROVED:** Prevent annoying FOUC ("flash of unstyled content") when loading directories in content pane. This makes me happy.
  226. // **IMPROVED:** Also show loading spinner while directory preview is loading; if a directory is inaccessible (perhaps because its permissions deny browser access) the spinner will remain visible.
  227. // **IMPROVED:** Clicked link handling in HTML files. After clicking a same-origin link, the item will open in the preview pane; closing the item will return to original HTML file.
  228. // - Non-same-origin links will open in a new tab/window.
  229. // **IMPROVED:** When sidebar is focused, content pane directory previews will be dimmed.
  230. // **IMPROVED:** Allow tab key to focus links and form elements in previewed html documents (including text editor preview).
  231. // **IMPROVED:** Show/Hide Invisibles from focused content pane with <kbd>Cmd/Ctr + I</kbd>.
  232. // **IMPROVED:** Content pane title now updates correctly when navigating history state for directories or txt/html files.
  233. // **ADDED:** Warning when attempting to open local links from a non-local page from an html file.
  234. // **CHANGED:** "Show Ignored Files" pref default to "true" because it could be confusing when using the script for the first time if those items were invisible.
  235. // **UPDATED:** markdown-it.js > 11.0.0, markdown-it-multimd-table.js > 4.0.2.
  236. // Many more fixes and improvements.
  237.  
  238. // **VERSION 5.0.1:**
  239. // **FIXED:** Parent Directory link for previewed directories wasn't working.
  240. // **IMPROVED:** Content pane title didn't update after clicking same-origin links in previewed html.
  241.  
  242. // **VERSION 5.0.0** Lotsa stuff....
  243. // **MAJOR INTERNAL CHANGES**
  244. // - Converted many "if ... else" conditional statements to "switch" statements for clarity and efficiency.
  245. // - Converted most <code><table></code> HTML elements in the UI to flex box <code><divs></code>, because it's 2020....
  246. // - Converted css to selector-based system and pruned many unneeded styles.
  247. // **IMPROVED:** Significantly improved performance of initial directory processing; this is especially noticable for directories with many items.
  248. // **CONTENT PANE:**
  249. // **PREVIEWED DIRECTORIES**
  250. // **ADDED:** Keyboard navigation for previewed directories.
  251. // - Use <kbd>Tab</kbd> to toggle focus between sidebar and content pane (for images, fonts, grids, html and text files, and the text editor).
  252. // - Use <kbd>Escape</kbd> to focus sidebar from content pane.
  253. // - Use arrow keys to navigate items as in sidebar.
  254. // - Use <kbd>Cmd/Ctrl</kbd> + <kbd>ArrowUp</kbd> to go to parent directory in content pane.
  255. // - Previewed directories honor "show invisibles" and "ignore ignored files" pref.
  256. // **ADDED:** Double-click or (<kbd>Cmd/Ctr + Return) items in previewed directories to open them.
  257. // **ADDED:** Continuous audio playback from previewed directories.
  258. // **ADDED:** Dark theme for directory preview.
  259. // **ADDED:** "Open in Content Pane" and "View Directory Source" links in sidebar footer.
  260. // - "Open in Content Pane" will show the current directory in the content pane.
  261. // - (This is useful in situations were the currently previewed directory contains only files and you want to navigate directories in the content pane.)
  262. // **IMPROVED:** Directory list now uses custom file icons for media files, because otherwise it's difficult to tell audio and video files apart.
  263. // **HTML CONTENT:**
  264. // **FIXED:** Clicking links in previewed html files didn't do anything. Now internal #links or same-origin links will open in the content pane; links to external sites will load in a new tab/window.
  265. // **PLAYLISTS**
  266. // **ADDED:** "Make Playlist" menu item. Download a basic (no timings) .m3u playlist of the media files in the current directory (with options for audio or video files only).
  267. // **ADDED:** Click audio player title to show an m3u playlist entry for the current media file.
  268. // **IMPROVED:** Added warning when opening a playlist with local files from a non-local page.
  269. // - Playlist will open, but local files will be disabled.
  270. // **ADDED:** Custom support for directories and _any_ file type in .m3u playlists.
  271. // - "Make Playlist" menu item has options for all files and directories, all non-media files, files only, and directories only.
  272. // - You can also click the content pane title to show an m3u playlist entry for the selected file.
  273. // **TEXT EDITING**
  274. // **ADDED:** New user pref to use light or dark theme for text editor instead of main UI theme.
  275. // - Text editor theme can be toggled with new button in text editor toolbar.
  276. // **ADDED:** Keyboard shortcut to toggle text editor split view: <kbd>Cmd/Ctrl + \</kbd>.
  277. // **IMPROVED:** Text editor now remembers insertion/caret position or selection range (until page is reloaded or URL changes).
  278. // - Position and range are remembered even if you close the text editor and view other content in the current directory, or if you navigate to another directory from within the preview pane.
  279. // **IMPROVED:** Better handling of unicode text.
  280. // **KEYBOARD SHORTCUTS:**
  281. // **CHANGED:**
  282. // - <kbd>Cmd|Ctrl + E</kbd>: Toggle main menu.
  283. // - <kbd>Cmd|Ctrl + Shift + E</kbd>: Show text editor.
  284. // **ADDED:** <kbd>Cmd|Ctrl + \</kbd>: toggle sidebar.
  285. // **IMPROVED:** <kbd>Escape</kdb> now also closes menus.
  286. // **IMPROVED:** Allow normal <kbd>Cmd|Ctrl + +/–</kbd> behavior if no scalable content (images, grids, etc.) is visible; allows scaling of browser window contents.
  287. // **UI**
  288. // **ADDED:** Keyboard navigation for the main menu.
  289. // - Use Up/Down Arrows to select items, Left/Right Arrows to open submenus.
  290. // - <kbd>Return/Enter</kbd>: Choose selected item.
  291. // **ADDED:** Toggle "Ignore ignored files" menu item.
  292. // **ADDED:** Basic Help menu item (shows keyboard shortcuts, etc.)
  293. // **ADDED:** More tooltips for various UI elements.
  294. // **IMPROVED:** Completely overhauled warnings html and styles.
  295. // **IMPROVED:** Added more info to stats in sidebar footer:
  296. // - On hover, show total number of each kind of file or dir, and how many are hidden.
  297. // - Live updates when show/hide invisibles or show/hide ignored files settings are changed.
  298. // - Stats update when playlist is loaded.
  299. // **IMPROVED:** Tab key behavior; tab now also toggles focus of previewed images, font specimen text, and text editor, as well as html and .txt/.md files.
  300. // **ADDED:** Allow arrow keys to scroll scaled/zoomed images when focused.
  301. // **FIXED:** Allow sidebar arrow navigation when sidebar is hidden.
  302. // **FIXED:** Toggle media checkbox wasn't working.
  303. // **FIXED:** An issue with <kbd>?</kbd>s in file names.
  304. // **FIXED:** <kdb>Cmd|Ctrl + W</kbd> now closes tab/window if no content is being previewed.
  305. // **FIXED:** Arrow navigation for sidebar and previewed directories sometimes used default arrow behavior, preventing proper scroll into view behavior.
  306. // **IMPROVED:** Workaround for Chrome bug with scroll into view multiple elements simultaneously (e.g., scrolling selected sidebar and grid items); scrolling is "instant" instead of "smooth."
  307. // **CHANGED:** "Ignore ignored files" pref behavior.
  308. // - If "true", ignored files will be hidden. If "false", ignored files will be shown, but will not be downloaded when clicked or navigated to.
  309. // **FIXED:** Unchecked media files would be selected and played in some circumstances.
  310. // **FIXED & IMPROVED:** Various style changes and fixes.
  311. // - Increased UI contrast overall.
  312. // - Improved UI icon visibility.
  313. // - File icons weren't visible in the preview titlebar in some circumstances.
  314. // **IMPROVED:** Warn before closing previewed font files and playlists.
  315. // **IMPROVED:** Reloading previously-selected directory when navigating to parent folder now uses row id and so is not affected is the sort is changed.
  316. // **IMPROVED:** Directory list now remembers sort direction.
  317. // **IMPROVED:** HTTP error pages now load in the content pane and with original formatting.
  318. // **FIXED:** PDFs now load correctly if a pdf has already been loaded.
  319. // **OTHER:**
  320. // - Various fixes for Firefox.
  321. // - Choosing "Default User Settings" now does not remove history from query string.
  322. // - Removed m3u from "Text" file types.
  323. // - Many, many other fixes and improvements.
  324. //
  325. // **KNOWN ISSUES:**
  326. // **Chrome:** Smooth scroll (for scrollIntoView() ) seems to be broken in the latest releases.
  327. // **Firefox:** Clicking media checkboxes (to toggle selection) causes page reload.
  328. // **Firefox:** Tabbing into previewed directories in content pane doesn't work; click in content pane instead in order to use arrow navigation.
  329.  
  330. // ************ J + M + J ************* //
  331.  
  332. // ************************************ //
  333. // DON'T EDIT ANYTHING BELOW THIS LINE. //
  334. // ************************************ //
  335.  
  336. // ***** GENERAL SETUP ***** //
  337. function getBrowser() {
  338. switch(true) {
  339. case navigator.userAgent.search('Chrome') >= 0: return 'is_chrome';
  340. case navigator.userAgent.search('Firefox') >= 0: return 'is_gecko';
  341. case navigator.userAgent.search('MSIE') >= 0: return 'is_explorer';
  342. case navigator.userAgent.search('Opera') >= 0: return 'is_opera';
  343. case navigator.userAgent.search('Safari') >= 0 && navigator.userAgent.search('Chrome') < 0: return 'is_safari';
  344. }
  345. }
  346. function getOS() { // modded from https://stackoverflow.com/questions/38241480/detect-macos-ios-windows-android-and-linux-os-with-js
  347. var platform = window.navigator.platform, macos_platforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], windows_platforms = ['Win32', 'Win64', 'Windows', 'WinCE'], os = null;
  348. switch(true) {
  349. case macos_platforms.indexOf(platform) !== -1: os = 'macos'; break;
  350. case windows_platforms.indexOf(platform) !== -1: os = 'windows'; break;
  351. // case iosPlatforms.indexOf(platform) !== -1: os = 'ios'; break; // just in case;
  352. // case /Android/.test(userAgent): os = 'android'; break; // just in case;
  353. case !os && /Linux/.test(platform): os = 'linux'; break;
  354. }
  355. return os;
  356. }
  357. // OTHER
  358. // var context = new AudioContext(); // needed to allow continuous playback of audio
  359.  
  360. // PATHS
  361. const newURL = function(link) {
  362. try { return new URL(link,document.baseURL); }
  363. catch(error) { return; } //console.log('This link is invalid. Please check the file.'); }
  364. };
  365. function decodeURIComponentSafe(str) { // Fix "%" error in file name; see https://stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser
  366. if ( !str ) { return str; }
  367. try {
  368. return decodeURIComponent(str.replace(/%(?![0-9a-fA-F]{2})/g, '%25') ); // replace % with %25 if not followed by two a-f/number
  369. } catch(e) {
  370. return str;
  371. }
  372. }
  373. const $protocol = window.location.protocol;
  374. const $origin = $protocol +'//'+ window.location.host;
  375. let current_location = decodeURIComponentSafe( [location.protocol, '//', location.host, location.pathname].join('') );
  376. const current_dir_path = current_location.replace(/([/|_|—])/g,'$1<wbr>').replace(/\\/g,'/'); // URL w/o query string for display
  377. const current_dir = current_location.split('/').slice(-2,-1).toString();
  378.  
  379. function escapeStr(str) { str = str.replace(/([$?*+()[]|^])/g,'\\$1'); return str; }
  380.  
  381. // if URL is a file, change window location to parent dir, add querystring of file name; then autoload file.
  382. function loadFile() {
  383. let query_prefs = getQueryPrefs();
  384. query_prefs.set( 'file', current_location.slice(current_location.lastIndexOf('/') + 1) );
  385. window.location = current_location.slice(0,current_location.lastIndexOf('/') + 1) +'?'+ query_prefs;
  386. return;
  387. }
  388. if ( current_location.slice(current_location.lastIndexOf('/')).indexOf('.') !== -1 && !current_location.endsWith('/') && window.top === window.self ) {
  389. loadFile();
  390. }
  391. // QUERY PREFS
  392. function getQueryPrefs() { return new URL(window.location).searchParams; }
  393. // const initialQueryPrefs = getQueryPrefs();
  394. // set query key/value
  395. function setQuery(key, value) { let query_prefs = getQueryPrefs(); query_prefs.set( key, value ); updateQuery(query_prefs); }
  396. // get query value
  397. function getQuery(key) {
  398. let query_prefs = getQueryPrefs(), value = '';
  399. if ( key === 'width' ) { // set the stored sidebar width or use 30%
  400. value = ( !query_prefs.has(key) || window.innerWidth === 0 ? 30 : Math.round(100 * Number.parseInt(query_prefs.get('width'))/window.innerWidth) ); // percentage
  401. } else { // if the query_string has a key/value pair, use it, otherwise use the key/value pair from the $settings
  402. value = ( query_prefs.has(key) ? query_prefs.get(key) : $settings[key] !== undefined ? $settings[key].toString() : '' );
  403. value = value.replace('%2F','').replace('/',''); // some servers add a '/' to end of query string
  404. }
  405. return value;
  406. }
  407. // toggle query key
  408. function toggleQuery(key) {
  409. let query_prefs = getQueryPrefs(), non_bool_prefs = {
  410. 'theme_light': {'theme':'dark'},
  411. 'theme_dark': {'theme':'light'},
  412. 'source_text': {'default_text_view':'preview_text'},
  413. 'preview_text': {'default_text_view':'source_text'},
  414. 'sort_by_default': {'sort_by':'default'},
  415. 'sort_by_name': {'sort_by':'name'},
  416. 'sort_by_size': {'sort_by':'size'},
  417. 'sort_by_date': {'sort_by':'date'},
  418. 'sort_by_kind': {'sort_by':'kind'},
  419. 'sort_by_ext': {'sort_by':'ext'}
  420. };
  421. var value, query_value, settings_value;
  422. if ( non_bool_prefs[key] !== undefined ) {
  423. value = Object.values(non_bool_prefs[key]).toString();
  424. key = Object.keys(non_bool_prefs[key]).toString(); // must come after value: i.e., don't redefine key before getting value
  425. if ( $settings[key] === value ) { query_prefs.delete( key ); } else { query_prefs.set( key, value ); }
  426. } else {
  427. query_value = query_prefs.get(key);
  428. settings_value = $settings[key];
  429. value = ( query_value === null ? settings_value.toString() : query_value.toString() );
  430. value = ( value === 'true' ? 'false' : 'true' );
  431. if ( ( query_value !== null && query_value !== settings_value ) ) {
  432. query_prefs.delete( key );
  433. } else {
  434. query_prefs.set( key, value );
  435. }
  436. }
  437. updateQuery(query_prefs);
  438. }
  439. // remove query key
  440. function removeQuery(key) { let query_prefs = getQueryPrefs(); query_prefs.delete(key); updateQuery(query_prefs); }
  441. // update query string
  442. function updateQuery(query_str) {
  443. query_str = query_str.toString().replace('%2F','').replace('/','');
  444. if ( query_str.length > 0 ) { window.history.replaceState({}, document.title, window.location.pathname +'?'+ query_str); }
  445. updateParentLinks();
  446. }
  447.  
  448. // ***** SET UP UI ELEMENTS ***** //
  449. // Parent and Parents Menus
  450. // UTILITIES
  451. function updateParentQueryStr(str) { // decrement selected and history values
  452. let query_str = new URLSearchParams(str); // make new search params from window.location.search
  453. let history = ( query_str.has('history') ? query_str.get('history') : undefined );
  454. if ( history !== undefined ) {
  455. history = history.split(' ');
  456. switch(true) {
  457. case history.length > 1: query_str.set('selected',history[0]); history.shift(); query_str.set('history',history.join('+')); break;
  458. case history.length === 1: query_str.set('selected',history[0]); history.shift(); query_str.delete('history'); break;
  459. }
  460. } else {
  461. query_str.delete('selected');
  462. }
  463. str = decodeURIComponentSafe(query_str.toString());
  464. return str;
  465. }
  466. // create links
  467. function updateParentLinks() { $('#parents_dir_nav').siblings('ul').empty().append( createParentLinkItems() ); }
  468. function createParentLinks() {
  469. let link, links = [], query_str = window.location.search.toString().slice(1);
  470. let link_pieces = current_location.split('/'); // make array of parent directories
  471. link_pieces = link_pieces.slice(2,-2); // remove beginning and ending empty elements and current directory
  472. while ( link_pieces.length > 0 ) { // while there are link pieces...
  473. query_str = updateParentQueryStr(query_str); // update selected and history
  474. link = $protocol +'//'+ link_pieces.join('/') + '/?' + query_str; // assemble link
  475. links.push(link); // add to link array
  476. link_pieces.pop(); // remove last link piece and repeat...
  477. }
  478. return links;
  479. }
  480. // create menu items
  481. function createParentLinkItems() {
  482. let parent_link_menu_items = [], links = createParentLinks();
  483. $('#parent_dir_nav').find('a').attr( 'href', links[0] ); // set parent link
  484. for ( let i = 0; i < links.length; i++ ) {
  485. let display_name = links[i].split('/?')[0];
  486. display_name = display_name.replace(/\//g,'\/<wbr>');
  487. let menu_item = '<li><a href="'+ links[i] +'" class="text_color_111">' + display_name + '/</a></li>';
  488. parent_link_menu_items.push(menu_item);
  489. }
  490. return parent_link_menu_items; // return parents link items
  491. }
  492. // MENUS: User bookmarks
  493. function bookmarksMenuItems() {
  494. const bookmarks = $settings.bookmarks;
  495. let menu_items = [], links_arr = [], links_arr_str = '', links;
  496. if ( bookmarks.length > 0 ) {
  497. for ( let i = 0; i < bookmarks.length; i+=1 ) {
  498. links = bookmarks[i].links;
  499. // make array of links
  500. for ( let j = 0; j < links.length; j+=1 ) {
  501. if ( !links[j].link.endsWith('/') ) {
  502. links[j].link = links[j].link.slice(0,links[j].link.lastIndexOf('/') + 1) + '?file=' + links[j].link.slice(links[j].link.lastIndexOf('/') + 1) ;
  503. }
  504. links[j].link_name = links[j].link_name.split('/').join('/<wbr>');
  505. links_arr[j] = '<li><a class="menu_item has_icon_before text_color_111" href="'+ links[j].link +'">' + links[j].link_name + '</a></li>';
  506. }
  507. links_arr_str = links_arr.join('');
  508. menu_items[i] = '<li class="bookmark has_submenu"><a class="menu_item text_color_111">'+ bookmarks[i].menu_title +'</a><ul class="submenu background_color_D0_50 border_all">'+ links_arr_str +'</ul></li>';
  509. }
  510. menu_items = menu_items.join('');
  511. }
  512. return menu_items;
  513. }
  514. // MENUS: Other menu items
  515. // #menu li a::before, .toggle_UI_pref, a, a:, div, li, span, span::before, span::after, #dir_list a.icon span, #warnings h3::before, #content_audio_title td:before
  516. const SidebarMenuItems = function() {
  517. let sort_by = '<li id="sort_by" class="has_submenu border_top border_bottom"><span class="menu_item">Sort by&hellip;</span><ul id="sort_menu" class="submenu background_color_D0_50 border_all text_color_111"><li id="name"><span class="menu_item">Name</span></li><li id="size"><span class="menu_item">Size</span></li><li id="date"><span class="menu_item">Date</span></li><li id="kind"><span class="menu_item">Kind</span></li><li id="ext"><span class="menu_item">Extension</span></li><li id="default"><span class="menu_item">Default</span></li></ul>';
  518. let autoload_media = '<li id="autoload_media" class="toggle_UI_pref border_bottom" title="Automatically select and load the first media item in a directory."><span id="autoload_media_menu" class="menu_item">Autoload Media</span></li>';
  519. let theme = '<li title="Set the main UI theme (light or dark)."><span id="theme" class="menu_item toggle_UI_pref"><span> Theme</span></span></li>';
  520. let alternate_background = '<li id="alternate_background" class="toggle_UI_pref" title="Alternate backgrounds of directory items."><span class="menu_item">Alternate Backgrounds</span></li>';
  521. let show_numbers = '<li id="show_numbers" class="toggle_UI_pref border_bottom" title="Number directory list items."><span class="menu_item">Show Numbers</span></li>';
  522. let ignored_files = '<li id="ignored_files" class="has_submenu border_bottom"><span class="menu_item">Ignored Items</span><ul id="" class="submenu background_color_D0_50 border_all"><li id="hide_ignored_items" class="toggle_UI_pref border_bottom" title="Show/hide ignored items (from the list of ignored file types in the user settings)."><span class="menu_item">Hide Ignored Items</span></li><li id="ignore_ignored_items" class="toggle_UI_pref border_bottom" title="If checked, the browser will not attempt to load ignored items (from the list of ignored file types in the user settings). It is recommended to leave this checked."><span class="menu_item">Ignore Ignored Items</span></li></ul></li>';
  523. let text_editing = '<li id="text_editing" class="has_submenu"><span class="menu_item">Text Editing</span><ul id="text_editing_menu" class="submenu background_color_D0_50 border_all"><li id="text_editor_menu_item" class="border_bottom" title="Toggle the main text editor."><span id="text_editor" class="menu_item">Toggle Text Editor </span></li><li id="split_view" class="toggle_UI_pref border_bottom" title="Toggle display of default text view and both source and rendered text."><span id="toggle_split_view" class="menu_item">Split View</span></li><li id="preview_text_menu_item" title="Set the default text view for non-split view."><span class="toggle_UI_pref menu_item" id="source_text">Source Text</span><span class="toggle_UI_pref menu_item" id="preview_text">Preview Text</span></li></ul>';
  524. let disable_text_editing = '<li class="border_bottom" title="Enable/disable editing of plain text files. Does not effect main text editor."><span class="menu_item toggle_UI_pref" id="disable_text_editing"><span id="disable">Text Editing </span></span></li>';
  525. let open_playlist = '<li><label id="open_playlist_label" class="menu_item" for="open_playlist" title="Open local .m3u playlist/filelist file.">Open Playlist/Filelist&hellip;</label><input type="file" id="open_playlist" name="open_playlist" accept=".m3u,.m3u8"></input></li>';
  526. let make_playlist = '<li class="border_bottom"><a id="make_playlist" class="menu_item text_color_111" href="#" title="Make an .m3u playlist/filelist of the items in the current directory (if any).">Make Playlist/Filelist&hellip;</a></li>';
  527. let open_font = '<li class="border_bottom"><label id="open_font_label" class="menu_item" for="open_font" title="Open font file (.oft, .ttf, .woff) to view glyph repertoire and font info; save individual glyphs as .svg.">Open Font&hellip;</label><input type="file" id="open_font" name="open_font" accept=".otf,.ttf,.woff"></input></li>';
  528. let default_settings = '<li><a id="default_settings" class="menu_item text_color_111" href="#" title="Delete UI prefs stored in the URL query string and reload page.">Default User Settings</a></li>';
  529. let export_settings = '<li class="border_bottom"><a id="export_settings" class="menu_item text_color_111" href="#" title="Export hard-coded user settings and bookmarks to text file.">Export User Settings</a></li>';
  530. let about_link = '<li id="about"><a class="menu_item text_color_111" href="https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser" target="_blank">About</a></li>';
  531. let help_link = '<li id="show_help"><span class="menu_item text_color_111">Help</span></li>';
  532. let contact_link = '<li id="contact"><a class="menu_item text_color_111" href="mailto:mshroud@vivaldi.net">Contact</a></li>';
  533. let donate_link = '<li id="donate"><a class="menu_item text_color_111" href="https://paypal.me/mschrauzer" target="_blank" rel="noopener">Donate</a></li>';
  534. return sort_by + theme + alternate_background + show_numbers + ignored_files + autoload_media + text_editing + disable_text_editing + open_playlist + make_playlist + open_font + default_settings + export_settings + about_link + help_link + contact_link + donate_link;
  535. };
  536. const SidebarHeaderEls = function() {
  537. let checked = '';
  538. if ( getQuery('show_invisibles') === 'true' ) { checked = 'checked'; }
  539. let parent_dir_nav = '<nav id="parent_dir_nav" class="invert"><a class="menu_item" href="" title="Parent Directory">&nbsp;</a></nav>';
  540. let parents_dir_nav = '<nav id="parents_dir_nav" class="border_right border_left"><div id="current_dir_path" title="Parent Directories">'+ current_dir_path +'</div></nav><ul id="parents_links" class="menu background_color_D0_50 border_bottom"></ul>';
  541. let menu_nav = '<nav id="menu_nav" class="invert"><div>&nbsp;</div></nav><ul id="menu" class="menu background_color_D0_50 border_bottom">'+ bookmarksMenuItems() + SidebarMenuItems() +'</ul>';
  542. let show_details = '<button id="show_details" class="toggle_UI_pref" tabindex="-1" title="Toggle display of directory item detail information"><span id="show"> details</span></button>'; // "Show/Hide" prefixed in pseudo-element
  543. let inv_checkbox = '<label id="show_invisibles_container"><input class="toggle_UI_pref" type="checkbox" id="show_invisibles" for="inv_checkbox" name="inv_checkbox" tabindex="-1" '+ checked +' /><span class="text_color_111">Show Invisibles</span></label>';
  544. let grid_btn = '<div id="grid_btn" class="has_background" tabindex="-1" title="Show Grid"><ul class="menu has_popout_menu"><li id="show_image_grid" class="border_right border_bottom">Show Image Grid</li><li id="show_font_grid" class="border_right">Show Font Grid</li></ul></div>';
  545. let sorting = '<div id="sorting" class="background_color_C0_40"><div id="sorting_row_1" class="container"><div class="toggle_UI_pref name sorting" id="sort_by_name" title="Sort by name" colspan="2"><span><input id="play_toggle" type="checkbox" tabindex="-1" checked="true" />Name</span></div><div class="toggle_UI_pref sorting" id="sort_by_default" title="Default sort" colspan="2"><span>Default</span></div></div><div id="sorting_row_2"><div class="toggle_UI_pref details sorting" id="sort_by_size" title="Sort by size"><span>Size</span></div><div class="toggle_UI_pref details sorting" id="sort_by_date" title="Sort by size"><span>Date</span></div><div class="toggle_UI_pref details sorting" id="sort_by_kind" title="Sort by kind"><span>Kind</span></div><div class="toggle_UI_pref details sorting" id="sort_by_ext" title="Sort by extension"><span>Ext</span></div></div></div>';
  546. let text_editor_row = '<div id="text_editor_row" class="border_top background_color_C0_40"><a href="#" class="text_color_111" title="Toggle Text Editor">Text Editor</a></div>';
  547. let sidebar_header_head = '<div id="sidebar_title" class="border_bottom background_color_B0_30"><div></div></div>';
  548. let sidebar_menus = '<div id="sidebar_menus" class="background_color_B0_30 border_bottom"><div id="parent_dir_menu">'+ parent_dir_nav +'</div><div id="parents_dir_menu">'+ parents_dir_nav +'</div><div id="menu_container" title="Main menu">'+ menu_nav +'</div></div>';
  549. let sidebar_buttons = '<div id="sidebar_buttons" class="background_color_C0_40 border_bottom"><div id="sidebar_buttons_left" colspan="3">'+ show_details + inv_checkbox +'</div>'+ grid_btn +'</div>';
  550. let sidebar_header_body = '<div id="sidebar_header_body" class="border_bottom">'+ sidebar_menus + sidebar_buttons + sorting + text_editor_row +'</div>';
  551. let sidebar_header_els = '<section id="sidebar_header" class="text_color_111">'+ sidebar_header_head + sidebar_header_body +'</section>';
  552. return sidebar_header_els;
  553. };
  554. // Sidebar Footer (Stats)
  555. const SidebarFooterEls = function() {
  556. const stats_summary = '<div id="stats_summary" class="background_color_C0_40">&nbsp;</div>';
  557. const stats_summary_detailed_total = '<div id="stats_summary_detailed_total" class="summary_detailed border_bottom background_color_C0_40"></div>';
  558. const stats_summary_detailed_dirs = '<div id="stats_summary_detailed_dirs" class="dir summary_detailed"></div>';
  559. const stats_summary_detailed_files = '<div id="stats_summary_detailed_files" class="file summary_detailed border_bottom"></div>';
  560. const stats_summary_playlist_container = '<div id="stats_summary_playlist_container" class="background_color_C0_40"><div id="stats_summary_playlist_files" class="summary_detailed"></div></div>';
  561. const stats_summary_detailed_container = '<div id="stats_summary_detailed_container" class="background_color_E0_50">' + stats_summary_detailed_total + stats_summary_detailed_dirs + stats_summary_detailed_files +'</div>';
  562. const stats_details_container = '<div id="stats_details_container" class="background_color_E0_50"></div>';
  563. const stats = stats_summary + stats_summary_detailed_container + stats_summary_playlist_container + stats_details_container;
  564. let dir_list_foot = '<section id="tfoot" class="background_color_D0_50 border_top"><div id="stats" class="text_color_111" title="Click to toggle additional information">'+ stats +'</div><div id="footer_links" class="has_background invert">&nbsp;<ul class="has_popout_menu invert"><li id="open_in_content_pane" class="border_bottom text_color_111">Open Sidebar in Content Pane</li><li id="view_directory_source" class="text_color_111" data-kind="view_directory_source">View Sidebar Directory Source</li></ul></div></section>';
  565. return dir_list_foot;
  566. };
  567. // Dir List Elements
  568. const SidebarDirListEls = function() {
  569. let dir_list_body = '<tbody id="tbody" class="background_color_DD_44 text_color_111" tabindex="0"></tbody>';
  570. let sidebar_dir_list_els = '<section id="dir_list_wrapper"><table id="dir_list">'+ dir_list_body +'</table></section>';
  571. return sidebar_dir_list_els;
  572. };
  573. // CONTENT PANE ELEMENTS
  574. const ContentHeaderEls = function() {
  575. let title_buttons_left = '<div id="title_buttons_left" class="title_left"><button id="reload_btn" tabindex="-1"><span></span></button><button id="prev_next_btns" class="split_btn" tabindex="-1"><span id="prev_btn"><span>&nbsp;</span></span><span id="next_btn"><span>&nbsp;</span></span></button></div>';
  576. let title = '<div id="title" class=""><span></span></div>';
  577. let title_buttons_right = '<div id="title_buttons_right" class="title_right"><button id="close_btn" tabindex="-1"><span></span></button><button id="scale" class="split_btn" tabindex="-1"><span id="decrease">&nbsp;</span><span id="increase">&nbsp;</span></button></div>';
  578. let content_playlist = '<div id="content_playlist" class="playlist_entry_container border_bottom"><textarea id="content_playlist_textarea" rows="3" spellcheck="false" /></div>';
  579. let content_title = '<div id="content_title" class="title border_bottom">'+ title_buttons_left + title + title_buttons_right +'</div>'+ content_playlist;
  580. let content_header_els = '<header id="content_header"><div class="text_color_111 background_color_B0_30"><div>'+ content_title + ContentAudioEls() +'</div></div></header>';
  581. return content_header_els;
  582. };
  583. // Content containers
  584. const ContentEls = function() {
  585. let content_grid = '<div id="content_grid" data-kind="grid"></div>';
  586. let content_text = '<div id="content_text" class="background_color_DD_33"></div>';
  587. let content_font = '<div id="content_font" class="content background_color_FF_11 text_color_111" spellcheck="false" data-kind="font">'+ ContentFontEls() +'</div>';
  588. let content_image = '<div id="content_image_container" class="content background_color_FF_11" data-kind="image"><img id="content_image" tabindex="0"/></div>';
  589. let content_video = '<video id="content_video" class="content background_color_FF_11 media" controls data-kind="video">Your browser does not support the video tag.</video>';
  590. let content_pdf = '<embed id="content_pdf" class="content" name="plugin" tabindex="0" data-kind="pdf"></embed>';
  591. let content_iframe = '<iframe id="content_iframe" class="content" name="content_iframe" sandbox="allow-scripts allow-same-origin allow-modals allow-popups" tabindex="0"></iframe>';
  592. let content_els = '<div id="content_container" class="background_color_EE_22">'+ content_grid + content_text + content_font + content_image + content_pdf + content_video + content_iframe +'</div>';
  593. return content_els;
  594. };
  595. // Content Audio Els
  596. const ContentAudioEls = function() {
  597. let prev_track = '<div id="prev_track" class="prev_next_track_btn audio_controls" title="Previous track">&nbsp;</div>';
  598. let next_track = '<div id="next_track" class="prev_next_track_btn audio_controls" title="Next track">&nbsp;</div>';
  599. let audio_player = '<audio id="audio" preload="auto" tabindex="0" controls controlsList="nofullscreen" >Sorry, your browser does not support HTML5 audio.</audio>';
  600. // let audio_player = '<iframe id="audio_iframe" sandbox="allow-scripts allow-same-origin allow-modals allow-popups"></iframe>';
  601. let loop = '<label id="loop_label"><input type="checkbox" id="loop" for="loop" name="loop" tabindex="0" />Loop</label>';
  602. let shuffle = '<label id="shuffle_label"><input type="checkbox" id="shuffle" for="shuffle" name="shuffle" tabindex="0" />Shuffle</label>';
  603. let audio_options = '<div id="audio_options">'+ loop + shuffle +'</div>';
  604. let close_audio = '<div id="close_audio" class="audio_controls" title="Close audio"></div>';
  605. let content_audio_playlist ='<div id="content_audio_playlist" class="playlist_entry_container border_bottom"><textarea id="content_audio_playlist_textarea" rows="3" spellcheck="false" /></div>';
  606. let content_audio_title = '<div id="content_audio_title" title="Click to toggle .m3u playlist entry."><span></span></div>';
  607. let content_audio_els = content_audio_title +'<div id="content_audio" class="border_bottom"><div id="audio_container">'+ prev_track + next_track + audio_player + close_audio +'</div>'+ audio_options +'</div>'+ content_audio_playlist;
  608. return content_audio_els;
  609. };
  610. // Content Font Els
  611. const ContentFontEls = function() {
  612. let sample_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789 [(!@#$%^&*;:)]';
  613. // let sample_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ<br />abcdefghijklmnopqrstuvwxyz<br />0123456789.,:;&@!#$%‰\'\"“‘”’()[]{}<>| ¦*/\^-–—_~…<br />ÆÁÂÀÄÃÅÇÐÉÊËÈÍÎÌÏÓÔÒÖÕŒÚÛÙÜÝŸ<br />æáâàäãåçðéêëèíîìïóôòöõœúûùüýÿfiflƒñÑßþÞ<br />†‡£¢¥Øø©®™¿¡«»¯´¶§•ªº¹³²‚„˜˛¸`·¯˘°¨⁄‹›<br />ı½¼¾+±×÷√=≈≠¬∞¤≤≥◊∆∂µ∏π∑Ω<br />■░▒▓│┤╣║╗╝┐└┴┬├─┼╚╔╩╦╠═╬┘┌█▄▀‗';
  614. let specimen = '<div id="specimen" class="specimen border_bottom_x" contenteditable="true" tabindex="0">'+ sample_string +'</div>';
  615. let specimen_2 = '<h1 id="specimen_2">Typography</h1><h4 id="specimen_2H4">The art of using types to produce impressions on paper, vellum, &amp;c.</h4>';
  616. let specimen_3 = '<h2 id="specimen_3">S P E C I M E N</h2>';
  617. let specimen_3H3 = '<h3 id="specimen_3H3">Typography is the work of typesetters (also known as compositors), typographers, graphic designers, art directors, manga artists, comic book artists, graffiti artists, and, now, anyone who arranges words, letters, numbers, and symbols for publication, display, or distribution.</h3>';
  618. 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.';
  619. let lorem = '<div id="lorem" class="lorem">'+ lorem_string +'</div>';
  620. let lorem_2 = '<div id="lorem_2" class="lorem">'+ lorem_string +'</div>';
  621. let lorem_3 = '<div id="lorem_3" class="lorem">'+ lorem_string +'</div>';
  622. let specimen_strings = '<div id="specimen_string_2" class="hamburger border_bottom_x" contenteditable="true" tabindex="0">'+ specimen_2 +'</div><div id="specimen_string_3" class="hamburger border_bottom_x" contenteditable="true">'+ specimen_3 + specimen_3H3 +'</div><div id="lorem_strings" contenteditable="true" tabindex="0">'+ lorem + lorem_2 + lorem_3 +'</div>';
  623. let font_specimen = '<div id="font_specimen">'+ specimen + specimen_strings +'</div>';
  624. let glyph_viewer_info = '<div id="glyph_viewer_info" class="background_color_D0_50 border_bottom_x invert"><button id="save_svg_hidden">Save SVG</button><div></div><button id="save_svg">Save SVG</button></div>';
  625. let glyph_viewer = '<div id="glyph_viewer" class="background_color_11_FF invert">'+ glyph_viewer_info +'</div>';
  626. let content_font_els = font_specimen + ContentFontViewer() + glyph_viewer;
  627. return content_font_els;
  628. };
  629. // Font Viewer
  630. const ContentFontViewer = function() {
  631. let glyphs_container = '<div id="glyphs_container"></div>';
  632. let font_viewer = '<div id="font_viewer">'+ glyphs_container +'</div>';
  633. return font_viewer;
  634. };
  635. // Iframe Dir Elements
  636. const ContentIframeDirEls = function(parentLink) {
  637. let parent_link_cell = '<div id="change_dirs" class="border_bottom background_color_B0_30 text_color_111"><span id="parent"><a href="'+ parentLink +'" class="text_color_111" style="padding-left:16px;" title="Go to parent directory">Parent Directory</a></span><span id="open_in_sidebar"><a href="" class="text_color_111" style="padding-right:16px;" title="Open this directory in sidebar">Open in Sidebar</a></span></div>';
  638. let sorting_row = '<div id="sorting_row_1" class="border_bottom text_color_111 background_color_C0_40"><div class="sorting" id="sort_by_name" title="Sort by name"><span>Name</span></div><div class="sorting" id="sort_by_default" title="Default sort"><span>Default</span></div></div><div id="sorting_row_2" class="border_bottom background_color_C0_40 text_color_111"><div class="sorting" id="sort_by_ext" title="Sort by extension"><span>Ext</span></div><div class="sorting" id="sort_by_size" title="Sort by size"><span>Size</span></div><div class="sorting" id="sort_by_date" title="Sort by date"><span>Date</span></div><div class="sorting" id="sort_by_kind" title="Sort by kind"><span>Kind</span></div></div>';
  639. let iframe_dir_els = '<header id="thead">'+ parent_link_cell + sorting_row +'</header><section id="iframe_dir_list_wrapper"><table id="dir_list"><tbody id="tbody" class="background_color_DD_44 text_color_111"></tbody></table></section>';
  640. return iframe_dir_els;
  641. };
  642. // Warnings
  643. const Warnings = function() {
  644. let warning_close_font = '<div id="warning_close_font" class="warning">Are you sure you want to close the font preview?</div>';
  645. let warning_unsaved_text = '<div id="warning_unsaved_text" class="warning">You have unsaved changes.</div>';
  646. let warning_clear_text = '<div id="warning_clear_text" class="warning">Are you sure you want to clear all your text?</div>';
  647. let warning_local_bookmark = '<div id="warning_local_bookmark" class="warning">Can\'t load local items from non-local pages. <br />&emsp;Please use your browser\'s bookmarks instead or enter the URL manually.</div>';
  648. let warning_local_file = '<div id="warning_local_file" class="warning">Can\'t load local items from non-local pages.</div>';
  649. let warning_close_playlist = '<div id="warning_close_playlist" class="warning">Are you sure you want to close the playlist?</div>';
  650. let warning_local_playlist = '<div id="warning_local_playlist" class="warning">This playlist contains local files. <br />&emsp;Please reload this playlist from a local page in order to play them.</div>';
  651. let warning_no_playlist = '<div id="warning_no_playlist" class="warning">Can’t make playlist: no qualified items found.</div>';
  652. let warning_make_playlist = '<div id="warning_make_playlist" class="warning"><form id="make_playlist_form" action="#"><fieldset><div><input name="make_playlist" type="radio" id="media_files_only" checked><label for="media_files_only">All media files</label></div><div class="indent"><input name="make_playlist" type="radio" id="audio_files_only"><label for="audio_files_only">Audio files only</label></div><div class="indent"><input name="make_playlist" type="radio" id="video_files_only"><label for="video_files_only">Video files only</label></div><div><input name="make_playlist" type="radio" id="all_non_media_files"><label for="all_non_media_files">All non-media items</label></div><div><input name="make_playlist" type="radio" id="all_items"><label for="all_items">All items</label></div><div class="indent"><input name="make_playlist" type="radio" id="directories_only"><label for="directories_only">Directories only</label></div><div class="indent"><input name="make_playlist" type="radio" id="files_only"><label for="files_only">Files only</label></div></fieldset></form></div>';
  653. let warning_buttons = '<div id="warning_buttons"><button id="warning_btn_dont_save">Don\'t Save</button><button id="warning_btn_cancel" >Cancel</button><button id="warning_btn_clear">Clear</button><button id="warning_btn_save">Save</button><button id="warning_btn_ok">OK</button></div>';
  654. let warnings = '<div id="warnings_header" class="text_color_111 background_color_D0_50"><h3 id="warning_header"><span>Warning:</span></h3><h3 id="make_playlist_header"><span>Make Playlist/Filelist</span></h3></div><div id="warnings" class="text_color_111 background_color_D0_50">'+ warning_close_font + warning_unsaved_text + warning_clear_text + warning_local_bookmark + warning_local_file + warning_close_playlist + warning_local_playlist + warning_no_playlist + warning_make_playlist +'</div><div id="warning_buttons_container" class="background_color_E0_60">'+ warning_buttons +'</div>';
  655. return warnings;
  656. };
  657. // Help
  658. const $content_help = `
  659. <header class="title text_color_111 border_bottom_x background_color_B0_30"><span>HELP</span><button id="close_help" class="focus"><span>Close</span></button></header>
  660. <section><p style="text-align:center; font-weight:bold;"><a href="https://openuserjs.org/scripts/gaspar_schot/Supercharged_Local_Directory_File_Browser" target="_blank">Script Homepage (openuserjs.org)</a></p></section>
  661. <table id="content_help" class="background_color_C0_40 text_color_111 border_top_x border_right_x border_left_x">
  662. <tbody class="">
  663. <tr><td class="kbd_shortcut text_color_111 border_right_x">KEY</td><td class="help_description text_color_111">DESCRIPTION</td></tr>
  664. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&uarr;</kbd> or <kbd class="background_color_E0_60">&darr;</kdb></kbd></td><td class="help_description text_color_111">Select the previous/next sidebar item or previewed directory item.<br />
  665. If audio is playing, and the previous/next file is also audio, the file will be highlighted but not loaded in the audio player; press <kbd class="background_color_E0_60">return</kbd> to load it.</td></tr>
  666. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&larr;</kbd> or <kbd class="background_color_E0_60">&rarr;</kbd></kbd></td><td class="help_description text_color_111">Select prev/next item of the same kind as the current selection.<br />
  667. If current selection is a media file, select and begin playback of the next media item.</td></tr>
  668. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8997;</kbd> + <kbd class="background_color_E0_60">&larr;</kbd> or <kbd class="background_color_E0_60">&#8594;</kbd></kbd></td><td class="help_description text_color_111">Skip audio/video ±10s</td></tr>
  669. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8997;</kbd> + <kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60">&larr;</kbd> or <kbd class="background_color_E0_60">&rarr;</kbd></kbd></td><td class="help_description text_color_111">Skip audio/video ±30s</td></tr>
  670. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">&uarr;</kbd></kbd></td><td class="help_description text_color_111">Go to parent directory</td></tr>
  671. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">&darr;</kbd></kbd></td><td class="help_description text_color_111">Open selected sidebar directory</td></tr>
  672. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">Escape</kbd></td><td class="help_description text_color_111">Close menus and help, unfocus textareas and content pane, etc.</td></tr>
  673. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">Return</kbd></td><td class="help_description text_color_111">Open selected directory, select file, or pause/play media.</td></tr>
  674. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">Space</kbd></td><td class="help_description text_color_111">Pause/Play media files (if media player loaded).</td></tr>
  675. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">Tab</kbd></td><td class="help_description text_color_111">Toggle focus between sidebar and content pane.</td></tr>
  676. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">D</kbd></td><td class="help_description text_color_111">Toggle file details (size, date modified, kind) in some index page types.</td></tr>
  677. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">E</kbd></td><td class="help_description text_color_111">Toggle main menu.</td></tr>
  678. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">E</kbd></td><td class="help_description text_color_111">Show text editor.</td></tr>
  679. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">G</kbd></td><td class="help_description text_color_111">Show or reload image or font grids.</td></tr>
  680. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">I</kbd></td><td class="help_description text_color_111">Toggle invisible files.</td></tr>
  681. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">O</kbd></td><td class="help_description text_color_111">Open selected sidebar item in new window/tab.</td></tr>
  682. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">R</kbd></td><td class="help_description text_color_111">Reload grids and previewed content, reset scaled images/fonts, reset media files to beginning.</td></tr>
  683. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">W</kbd></td><td class="help_description text_color_111">Close previewed content (doesn't work in all browsers; use close button instead), or close window if no content is being previewed.</td></tr>
  684. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60"><</kbd> or <kbd class="background_color_E0_60">></kbd></td><td class="help_description text_color_111">Scale preview items and grids.</td></tr>
  685. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">\\</kbd></td><td class="help_description text_color_111">Toggle sidebar.</td></tr>
  686. <tr><td class="kbd_shortcut text_color_111 border_right_x"><kbd class="background_color_E0_60">&#8679;</kbd> + <kbd class="background_color_E0_60">&#8984;/Ctrl</kbd> + <kbd class="background_color_E0_60">\\</kbd></td><td class="help_description text_color_111">Toggle text editor split view.</td></tr>
  687. </tbody>
  688. </table>
  689. <div>Main Menu
  690. <dl>
  691. <dt>Open Playlist/Filelist</dt>
  692. <dd>The script supports basic .m3u playlists of audio or video files, as well as custom "filelists" which can include directories and any file types supported by the browser.</dd>
  693. </dl>
  694. </div>`;
  695. // MD Build UI
  696. const TextEditingUIEls = function() {
  697. const toggle_theme_btn = '<li id="toggle_theme" class="toolbar_icon has_background" title="Toggle Editor Theme"></li>';
  698. const toggle_split_btn = '<li id="toggle_split" class="toolbar_icon has_background" title="Toggle Split"></li>';
  699. const toggle_src_btn = '<li id="show_source" class="toolbar_icon has_background" title="Toggle Source"></li>';
  700. const toggle_preview_btn = '<li id="show_preview" class="toolbar_icon has_background" title="Toggle Preview"></li>';
  701. const toggle_HTML_btn = '<li id="show_html" class="toolbar_icon has_background" title="Toggle HTML"></li>';
  702. const sync_scroll_el = '<li id="sync_scroll"><input name="sync_scroll" type="checkbox"><label for="sync_scroll">Sync Scroll</label></li>';
  703. const clear_text_btn = '<li id="clear_text" title="Clear Text">Clear</li>';
  704. const save_btn = '<li id="save_btn" title=""><ul class="menu has_popout_menu"><li id="save_text" class="border_right border_bottom" title="Save source text"><span id="save_text_link" target="_blank">Save Source</span></li><li id="save_HTML" class="border_right" title="Save rendered html"><span id="save_HTML_link" target="_blank">Save HTML</span></li></ul></li>';
  705. const toolbar = '<table id="toolbar"><tbody><tr><td><ul id="toolbar_buttons">'+ toggle_theme_btn + toggle_src_btn + toggle_preview_btn + toggle_split_btn + toggle_HTML_btn + sync_scroll_el + save_btn + clear_text_btn +'</ul></td></tr></tbody></table>';
  706. const text_source = '<textarea id="text_source" class="background_color_DD_33 text_color_111" tabindex="0"></textarea>';
  707. const text_preview = '<div id="text_preview" class="background_color_DD_33 text_color_111 markdown-body" tabindex="0"></div>';
  708. const html_preview = '<div id="html_preview" class="background_color_DD_33 text_color_111" tabindex="0"></div>';
  709. const text_editing_UI = toolbar +'<div id="text_container">'+ text_source + text_preview + html_preview +'<div id="text_editing_handle"></div></div>';
  710. return text_editing_UI;
  711. };
  712. // ASSEMBLE SIBEBAR & CONTENT PANE ELEMENTS
  713. const MainContent = function() {
  714. let width = Number(getQuery('width'));
  715. let handle = '<div id="handle"></div>';
  716. let toggle_sidebar = '<div id="toggle_sidebar" class="invert" title="Toggle Sidebar"></div>';
  717. let sidebar = '<div id="sidebar" class="background_color_C0_40">' + SidebarDirListEls() + SidebarFooterEls() +'</div>';
  718. let sidebar_wrapper = '<section id="sidebar_wrapper" class="border_right" style="width:'+ width +'%">'+ SidebarHeaderEls() + sidebar + handle + toggle_sidebar +'</section>';
  719. let content_pane = '<section id="content_pane" style="width:'+ ( 100 - width ) +'%">'+ ContentHeaderEls() + ContentEls() +'</section>';
  720. let utilities = '<section id="utilities"><div id="warnings_container" class="">'+ Warnings() +'</div><div id="help_container" class="background_color_E0_60">'+ $content_help +'</div></section>';
  721. let main_content = '<main id="main_content">'+ sidebar_wrapper + content_pane + utilities +'</main>';
  722. return $(main_content);
  723. };
  724. // DEFINE Content Elements
  725. const $main_content = MainContent();
  726. const $dir_list_body = $main_content.find('#tbody');
  727. const $dir_list = $main_content.find('#dir_list');
  728. const $content_pane = $main_content.find('#content_pane');
  729. const $audio_player = $main_content.find('#audio');
  730. const $content_grid = $main_content.find('#content_grid');
  731. const $content_font = $main_content.find('#content_font');
  732. const $content_image_container = $main_content.find('#content_image_container');
  733. const $content_image = $content_image_container.find('img');
  734. const $content_video = $main_content.find('#content_video');
  735. const $content_iframe = $main_content.find('#content_iframe');
  736.  
  737. // SVG UI ICONS
  738. // auto format dark icon colr
  739. function SVG_UI_Icon(icon_name) {
  740. let svg = SVG_UI_Icons[icon_name];
  741. return 'url("data:image/svg+xml;utf8,'+ svg +'")';
  742. }
  743. const SVG_UI_Icons = {
  744. 'arrow': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M4 4l12 6-12 6z\' /></svg>',
  745. 'arrow_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23DDDDDD\' d=\'M4 4l12 6-12 6z\' /></svg>',
  746. 'bookmark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4V2zm2 0v15l6-3 6 3V2H4z\' /></svg>',
  747. 'bookmark_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23DDDDDD\' d=\'M2 2c0-1.1.9-2 2-2h12a2 2 0 0 1 2 2v18l-8-4-8 4V2zm2 0v15l6-3 6 3V2H4z\' /></svg>',
  748. '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>',
  749. 'chevron_up': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 12 08\'><g transform=\'matrix(1,0,0,1,-3.843,-5.843)\'><path d=\'M10.707,7.05L10,6.343L4.343,12L5.757,13.414L10,9.172L14.243,13.414L15.657,12L10.707,7.05Z\' style=\'fill:rgb(16,16,16);fill-rule:nonzero;\'/></g></svg>',
  750. 'chevron_right': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 08 12\'><g transform=\'matrix(1,0,0,1,-6.086,-4)\'><path d=\'M12.95,10.707L13.657,10L8,4.343L6.586,5.757L10.828,10L6.586,14.243L8,15.657L12.95,10.707Z\' style=\'fill:rgb(16,16,16);fill-rule:nonzero;\'/></g></svg>',
  751. 'chevron_down': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 12 08\'><g transform=\'matrix(1,0,0,1,-4,-6.157)\'><path d=\'M9.293,12.95L10,13.657L15.657,8L14.243,6.586L10,10.828L5.757,6.586L4.343,8L9.293,12.95Z\' style=\'fill:rgb(16,16,16);fill-rule:nonzero;\'/></g></svg>',
  752. 'chevron_left': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 08 12\'><g transform=\'matrix(1,0,0,1,-5.843,-4)\'><path d=\'M7.05,9.293L6.343,10L12,15.657L13.414,14.243L9.172,10L13.414,5.757L12,4.343L7.05,9.293Z\' style=\'fill:rgb(16,16,16);fill-rule:nonzero;\'/></g></svg>',
  753. 'document': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M4 18h12V6h-4V2H4v16zm-2 1V0h12l4 4v16H2v-1z\' /></svg>',
  754. '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>',
  755. 'folder': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M0 4c0-1.1.9-2 2-2h7l2 2h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm2 2v10h16V6H2z\' /></svg>',
  756. 'grid': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  757. 'grid_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23DDDDDD\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  758. 'grid_loaded': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23118888\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  759. 'grid_loaded_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%2344BBBB\' d=\'M0 0h9v9H0V0zm2 2v5h5V2H2zm-2 9h9v9H0v-9zm2 2v5h5v-5H2zm9-13h9v9h-9V0zm2 2v5h5V2h-5zm-2 9h9v9h-9v-9zm2 2v5h5v-5h-5z\' /></svg>',
  760. 'ignored': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M0 10a10 10 0 1 1 20 0 10 10 0 0 1-20 0zm16.32-4.9L5.09 16.31A8 8 0 0 0 16.32 5.09zm-1.41-1.42A8 8 0 0 0 3.68 14.91L14.91 3.68z\' opacity=\'0.25\' /></svg>',
  761. 'ignored_dark': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23BBBBBB\' d=\'M0 10a10 10 0 1 1 20 0 10 10 0 0 1-20 0zm16.32-4.9L5.09 16.31A8 8 0 0 0 16.32 5.09zm-1.41-1.42A8 8 0 0 0 3.68 14.91L14.91 3.68z\' opacity=\'0.25\' /></svg>',
  762. 'menu': '<svg width=\'100%\' height=\'100%\' viewBox=\'0 0 13 10\' version=\'1.1\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' xml:space=\'preserve\' xmlns:serif=\'http://www.serif.com/\' style=\'fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;\'> <rect x=\'0\' y=\'0\' width=\'13\' height=\'2\' style=\'fill:rgb(34,34,34);\'/><rect x=\'0\' y=\'4\' width=\'13\' height=\'2\' style=\'fill:rgb(34,34,34);\'/><rect x=\'0\' y=\'8\' width=\'13\' height=\'2\' style=\'fill:rgb(34,34,34);\'/></svg>',
  763. 'minus': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><rect fill=\'%23222222\' x=\'1\' y=\'8\' width=\'18\' height=\'4\' /></svg>',
  764. 'multiply': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M10,7l6,-6l3,3l-6,6l6,6l-3,3l-6,-6l-6,6l-3,-3l6,-6l-6,-6l3,-3l6,6Z\'/></svg>',
  765. '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>',
  766. 'plus': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M8.001,1l3.999,0l0,7l7,0l0,4l-7,0l-0.001,7l-3.999,0l0,-7l-7,0l0,-4l7,0l0.001,-7Z\'/></svg>',
  767. 'prev_next_track': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M13,5l2,0l0,10l-2,0l0,-10Zm-8,0l8,5l-8,5l0,-10Z\'/></svg>',
  768. '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>',
  769. 'spinner': '<?xml version=\'1.0\' encoding=\'utf-8\'?><svg xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\' style=\'margin: auto; background: none; display: block; shape-rendering: auto;\' width=\'200px\' height=\'200px\' viewBox=\'0 0 100 100\' preserveAspectRatio=\'xMidYMid\'><g transform=\'translate(50 50)\'><g transform=\'rotate(44.0329)\'><animateTransform attributeName=\'transform\' type=\'rotate\' values=\'0;45\' keyTimes=\'0;1\' dur=\'0.25s\' repeatCount=\'indefinite\'/><path d=\'M29.491524206117255 -5.5 L37.491524206117255 -5.5 L37.491524206117255 5.5 L29.491524206117255 5.5 A30 30 0 0 1 24.742744050198738 16.964569457146712 L24.742744050198738 16.964569457146712 L30.399598299691117 22.621423706639092 L22.621423706639096 30.399598299691114 L16.964569457146716 24.742744050198734 A30 30 0 0 1 5.5 29.491524206117255 L5.5 29.491524206117255 L5.5 37.491524206117255 L-5.499999999999997 37.491524206117255 L-5.499999999999997 29.491524206117255 A30 30 0 0 1 -16.964569457146705 24.742744050198738 L-16.964569457146705 24.742744050198738 L-22.621423706639085 30.399598299691117 L-30.399598299691117 22.621423706639092 L-24.742744050198738 16.964569457146712 A30 30 0 0 1 -29.491524206117255 5.500000000000009 L-29.491524206117255 5.500000000000009 L-37.491524206117255 5.50000000000001 L-37.491524206117255 -5.500000000000001 L-29.491524206117255 -5.500000000000002 A30 30 0 0 1 -24.742744050198738 -16.964569457146705 L-24.742744050198738 -16.964569457146705 L-30.399598299691117 -22.621423706639085 L-22.621423706639092 -30.399598299691117 L-16.964569457146712 -24.742744050198738 A30 30 0 0 1 -5.500000000000011 -29.491524206117255 L-5.500000000000011 -29.491524206117255 L-5.500000000000012 -37.491524206117255 L5.499999999999998 -37.491524206117255 L5.5 -29.491524206117255 A30 30 0 0 1 16.964569457146702 -24.74274405019874 L16.964569457146702 -24.74274405019874 L22.62142370663908 -30.39959829969112 L30.399598299691117 -22.6214237066391 L24.742744050198738 -16.964569457146716 A30 30 0 0 1 29.491524206117255 -5.500000000000013 M0 -20A20 20 0 1 0 0 20 A20 20 0 1 0 0 -20\' fill=\'%237c7c7c\'/></g></g></svg>',
  770. 'toggle': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'><path fill=\'%23222222\' d=\'M10.207,9.293l-0.707,0.707l5.657,5.657l1.414,-1.414l-4.242,-4.243l4.242,-4.243l-1.414,-1.414l-4.95,4.95Z\' /><path fill=\'%23222222\' d=\'M4.207,9.293l-0.707,0.707l5.657,5.657l1.414,-1.414l-4.242,-4.243l4.242,-4.243l-1.414,-1.414l-4.95,4.95Z\'/></svg>',
  771. };
  772.  
  773. function SVG_UI_File_Icon(icon_name) {
  774. switch(icon_name) {
  775. case 'favicon':
  776. return 'iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUmRcmZzP8zmf8pVcWPAAAAAXRSTlMAQObYZgAAAFBJREFUeF7tyqERwDAMBEE3mX5UiqDmqwwziTPHjG7xrmzrLFtRaApDIRiKQlMYCsFQFJrCUAiGotAU5hTA1WB4fhkMBsOJwWAwgHvB8CHpBcTbpxy4RZNvAAAAAElFTkSuQmCC';
  777. case 'file_icon_dir_default': // default chrome dir and file icons
  778. 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= ")';
  779. case 'file_icon_file_default':
  780. return 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ")';
  781. default:
  782. return 'url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons[icon_name] +'")';
  783. }
  784. }
  785. const SVG_UI_File_Icons = { // n.b.: order is important
  786. '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>',
  787. '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>',
  788. '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>',
  789. '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>',
  790. 'file_icon_dirinvisible': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M6,2.5l-1,-1.5l-5,0l0,12l14,0l0,-10.5l-8,0Z\' style=\'fill:%23888;fill-rule:nonzero;\'/><rect x=\'1.5\' y=\'4\' width=\'11\' height=\'7.5\' style=\'fill:%23bbb;\'/><circle cx=\'7\' cy=\'7.5\' r=\'1.5\' style=\'fill:%23888;\'/></svg>',
  791. '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>',
  792. '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>',
  793. '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>',
  794. '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>',
  795. '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>',
  796. '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>',
  797. '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>',
  798. '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>',
  799. '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>',
  800. '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>',
  801. 'file_icon_ignoredimage': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><path d=\'M0.369,9.141c-0.252,-0.678 -0.369,-1.396 -0.369,-2.141c0,-3.863 3.137,-7 7,-7c3.863,0 7,3.137 7,7l-0.137,1.353l-3.853,-3.853l-3.5,3.5l-2.5,-2.5l-3.641,3.641Z\' style=\'fill:%23808080;\'/><path d=\'M0.839,10.151l-0.47,-1.01l3.641,-3.641l2.5,2.5l3.5,-3.5l3.853,3.853c-0.076,0.395 -0.201,0.778 -0.341,1.147l-10.371,3.345c-0.293,-0.194 -0.579,-0.416 -0.838,-0.651l-1.474,-2.043Z\' style=\'fill:%23fff;\'/><g><path d=\'M13.522,9.5c-0.99,2.64 -3.539,4.5 -6.522,4.5c-1.426,0 -2.753,-0.421 -3.849,-1.155l6.859,-6.866l3.512,3.521Z\' style=\'fill:%23808080;\'/><path d=\'M0.839,10.151l3.171,-3.172l1.761,1.761l-3.459,3.454c-0.591,-0.632 -1.079,-1.313 -1.473,-2.043Z\' style=\'fill:%23808080;\'/></g><circle cx=\'6\' cy=\'3.5\' r=\'1.5\' style=\'fill:%23fff;\'/></svg>',
  802. '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>',
  803. '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>',
  804. '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>',
  805. '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>',
  806. '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>',
  807. '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>',
  808. // the following are the same:
  809. 'file_icon_bin': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><g><path d=\'M1.247,6.495l3.263,0l0,-1.067l-0.881,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.052,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M7,6.588c1.082,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.743,-2.521 -1.825,-2.521c-1.082,0 -1.825,0.843 -1.825,2.521c0,1.677 0.743,2.567 1.825,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M9.598,6.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.283,0.479l0,0.82l0.927,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M2.825,12.588c1.082,0 1.824,-0.89 1.824,-2.567c0,-1.67 -0.742,-2.521 -1.824,-2.521c-1.083,0 -1.825,0.843 -1.825,2.521c0,1.677 0.742,2.567 1.825,2.567Zm0,-1.021c-0.31,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.262,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M5.423,12.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M11.175,12.588c1.083,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.742,-2.521 -1.825,-2.521c-1.082,0 -1.824,0.843 -1.824,2.521c0,1.677 0.742,2.567 1.824,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.31,0 0.572,0.201 0.572,1.5c0,1.299 -0.262,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></g></svg>',
  810. 'file_icon_other': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 14\'><rect x=\'0\' y=\'0\' width=\'14\' height=\'14\' style=\'fill:%23808080;fill-rule:nonzero;\'/><g><path d=\'M1.247,6.495l3.263,0l0,-1.067l-0.881,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.052,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M7,6.588c1.082,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.743,-2.521 -1.825,-2.521c-1.082,0 -1.825,0.843 -1.825,2.521c0,1.677 0.743,2.567 1.825,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M9.598,6.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.283,0.479l0,0.82l0.927,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M2.825,12.588c1.082,0 1.824,-0.89 1.824,-2.567c0,-1.67 -0.742,-2.521 -1.824,-2.521c-1.083,0 -1.825,0.843 -1.825,2.521c0,1.677 0.742,2.567 1.825,2.567Zm0,-1.021c-0.31,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.262,-1.5 0.572,-1.5c0.309,0 0.572,0.201 0.572,1.5c0,1.299 -0.263,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M5.423,12.495l3.263,0l0,-1.067l-0.882,0l0,-3.835l-0.974,0c-0.371,0.232 -0.727,0.371 -1.284,0.479l0,0.82l0.928,0l0,2.536l-1.051,0l0,1.067Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/><path d=\'M11.175,12.588c1.083,0 1.825,-0.89 1.825,-2.567c0,-1.67 -0.742,-2.521 -1.825,-2.521c-1.082,0 -1.824,0.843 -1.824,2.521c0,1.677 0.742,2.567 1.824,2.567Zm0,-1.021c-0.309,0 -0.572,-0.247 -0.572,-1.546c0,-1.299 0.263,-1.5 0.572,-1.5c0.31,0 0.572,0.201 0.572,1.5c0,1.299 -0.262,1.546 -0.572,1.546Z\' style=\'fill:%23CCC;fill-rule:nonzero;\'/></g></svg>',
  811. '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>'
  812. };
  813. // Programatically add File icon CSS rules
  814. function CSS_UI_Icon_Rules() {
  815. let rules = '', kind, class_name;
  816. rules += '#menu ul a::before { background-image:'+ SVG_UI_File_Icon('file_icon_file') +'; }';
  817. rules += '#menu ul a[href^="file"]::before { background-image:'+ SVG_UI_File_Icon('file_icon_dir') +'; }';
  818. rules += '#menu ul a[href^="http"]::before { background-image:'+ SVG_UI_File_Icon('file_icon_htm') +'; }';
  819. rules += 'body:not(.use_custom_icons) #dir_list tr.dir a.icon span::before { background-image:'+ SVG_UI_File_Icon('file_icon_dir_default') + '; background-size:auto 13px; }';
  820. rules += 'body:not(.use_custom_icons) #dir_list tr.file:not(.app) a.icon span::before { background-image:'+ SVG_UI_File_Icon('file_icon_file_default') + '; background-size:auto 13px; }';
  821. for ( let icon in SVG_UI_File_Icons ) {
  822. kind = icon.slice(icon.lastIndexOf('_') + 1);
  823. class_name = kind;
  824. // exceptions
  825. if ( kind === 'dirinvisible' ) { class_name = 'dir.invisible'; }
  826. if ( kind === 'ignoredimage' ) { class_name = 'ignored_image'; }
  827. if ( /alias|symlink/.test(kind) ) { class_name = 'link'; }
  828. // add rules for dir_list items, content_header, stats details:
  829. rules += 'body.use_custom_icons #dir_list tr.'+ class_name +' a.icon span::before, #content_pane[data-content="has_'+ class_name +'"] #title span::before, body.use_custom_icons #stats .stats_kind.'+ class_name +' span.has_icon_before::before { background-image: url("data:image/svg+xml;utf8,'+ SVG_UI_File_Icons['file_icon_'+kind] +'"); }';
  830. }
  831. return rules;
  832. }
  833. // Text Editing UI Icons
  834. function SVG_Text_Editing_UI_Icon(icon_name) {
  835. let svg = SVG_Text_Editing_UI_Icons[icon_name];
  836. return 'url("data:image/svg+xml;utf8,'+ svg +'")';
  837. }
  838. const SVG_Text_Editing_UI_Icons = {
  839. 'toggle_theme': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><path d=\'M8 0c4.415 0 8 3.585 8 8s-3.585 8-8 8-8-3.585-8-8 3.585-8 8-8zm0 2c3.311 0 6 2.689 6 6s-2.689 6-6 6V2z\' fill=\'%23333\'/></svg>',
  840. 'show_markdown': '<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'100\' height=\'60\'><g fill=\'%23333\'><path d=\'M42.215 60l.17-46.24h-.255L30.06 60h-7.99L10.255 13.76H10L10.169 60H.905V-.18H14.59l11.56 44.03h.34L37.794-.18H52.16V60h-9.945zM99.589 29.996c0 9.519-1.997 16.901-5.992 22.142C89.602 57.38 83.722 60 75.959 60H60.914V-.18h15.13c7.706 0 13.558 2.65 17.553 7.948 3.995 5.299 5.992 12.708 5.992 22.228zm-10.2 0c0-3.57-.326-6.686-.978-9.35-.651-2.663-1.572-4.873-2.762-6.63-1.19-1.756-2.607-3.073-4.25-3.953-1.645-.878-3.43-1.317-5.355-1.317h-4.845v42.33h4.845c1.926 0 3.711-.438 5.355-1.317 1.643-.878 3.06-2.195 4.25-3.953 1.189-1.756 2.11-3.952 2.762-6.587.651-2.637.978-5.709.978-9.223z\'/></g></svg>',
  841. 'show_source': '<svg viewBox=\'0 0 22 14\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><path fill=\'none\' d=\'M0 0h21.996v14H0z\'/><clipPath id=\'a\'><path d=\'M0 0h21.996v14H0z\'/></clipPath><g clip-path=\'url(%23a)\' fill=\'%23333\'><path d=\'M0 7.393v-.786l6.062-3.5.75 1.3L2.32 7l4.492 2.593-.75 1.3L0 7.393zM21.996 6.607v.786l-6.062 3.5-.75-1.3L19.676 7l-4.492-2.593.75-1.3 6.062 3.5zM15.15 1.313l-1.3-.75-7 12.124 1.3.75 7-12.124z\'/></g></svg>',
  842. 'show_preview': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><path d=\'M10 2.5V1H0v1.5h4V15h2V2.5h4zM9 6.5V8h2v4.053c0 2.211 1.547 3.442 3 3.442.989 0 1.556-.258 2-.495v-1.5c-.565.257-.882.376-1.507.376-.847 0-1.493-.474-1.493-1.876V8h2.5V6.5H13v-3h-1.98v3H9z\' fill=\'%23333\' fill-rule=\'nonzero\'/></svg>',
  843. 'show_html': '<svg viewBox=\'0 0 22 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><path fill=\'none\' d=\'M0 0h21.022v16H0z\'/><clipPath id=\'a\'><path d=\'M0 0h21.022v16H0z\'/></clipPath><g clip-path=\'url(%23a)\' fill=\'%23333\'><path d=\'M7.732.222L9.5 1.99 3.49 8l6.01 6.01-1.768 1.768L-.046 8 7.732.222zM13.268 15.778L11.5 14.01 17.51 8 11.5 1.99 13.268.222 21.046 8l-7.778 7.778z\'/></g></svg>',
  844. 'toggle_split': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><path d=\'M0 0v16h16V0H0zm14 14H9V2h5v12zm-7 0H2V2h5v12z\' fill=\'%23333\' fill-rule=\'nonzero\'/></svg>',
  845. 'save_btn': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><g fill=\'%23333\'><path d=\'M16 0v10.02L14 10V2H2v8l-2 .02V0h16z\' fill-rule=\'nonzero\'/><path d=\'M7 5h2v9H7z\'/><path d=\'M3.757 11.757l1.415-1.414L8 13.172l2.828-2.829 1.415 1.414L8 16l-4.243-4.243z\'/></g></svg>',
  846. 'save_btn_edited': '<svg viewBox=\'0 0 16 16\' xmlns=\'http://www.w3.org/2000/svg\' fill-rule=\'evenodd\' clip-rule=\'evenodd\' stroke-linejoin=\'round\' stroke-miterlimit=\'2\'><g fill=\'%23DD2222\'><path d=\'M16 0v10.02L14 10V2H2v8l-2 .02V0h16z\' fill-rule=\'nonzero\'/><path d=\'M7 5h2v9H7z\'/><path d=\'M3.757 11.757l1.415-1.414L8 13.172l2.828-2.829 1.415 1.414L8 16l-4.243-4.243z\'/></g></svg>'
  847. };
  848. //***** END UI ELEMENTS
  849.  
  850. //***** STYLES *****//
  851.  
  852. // DEFINE STYLES
  853. const $warning_styles =
  854. // WARNINGS
  855. '#warnings_container { width:26em; transform:translate(-50%, 0); display:none; flex-direction:column; border-radius:0 0 3px 3px; position:absolute; top:0; left:50%; z-index:9999; box-shadow:0px 2px 12px 0 #333; font-size:0.875em; color:#111; overflow:hidden; }' + // table
  856. 'body.has_warning #warnings_container { display:flex; }' +
  857. // warnings thead
  858. '#warnings_header { padding:1rem 1.5rem; background-position:left 1.25rem center; background-repeat:no-repeat; background-size:24px; }' +
  859. '#warnings_container:not(.warning_make_playlist) #warnings_header { background-image:'+ SVG_UI_Icon('error') +'; }' +
  860. '#warnings_header h3 { display:none; margin:0; text-indent:2.25em; }' +
  861. '#warnings_container:not(.warning_make_playlist) h3#warning_header, #warnings_container.warning_make_playlist h3#make_playlist_header { display:block;}' +
  862. 'body#top.edited #warnings_container.unloading h3::before { content: "Text Editor: " }' +
  863. // warnings tbody
  864. '#warnings .warning { padding: 0 1.5rem 1rem; display:none; hyphens:none; }' + // tbody
  865. // warnings tfoot
  866. '#warning_buttons_container { padding:1rem 1.5rem; }' +
  867. '#warning_buttons { display:flex; flex-direction:row; }' +
  868. '#warning_buttons button { min-width:4em; display:none; font-size:1em; }' +
  869. 'button.focus, button:focus { background-color: #0E4399; color: #EEE; outline:none; }' +
  870. '#warning_btn_dont_save { margin-right: auto; }' + // button
  871. '#warning_btn_cancel, #warning_btn_clear, #warning_btn_save { margin-left: 0.5rem; }'+ // button
  872. '#warning_btn_ok { margin-left:auto; }'+ // button
  873. '#warnings_container.warning_close_font #warning_close_font, #warnings_container.warning_close_playlist #warning_close_playlist, #warnings_container.unloading #warning_unsaved_text, #warnings_container.unloading #warning_btn_dont_save, #warnings_container.unloading #warning_btn_cancel, #warnings_container.unloading #warning_btn_save { display:inline-block; }'+ // button
  874. '#warnings_container.clear #warning_buttons { justify-content:space-between; }'+
  875. '#warnings_container.clear #warning_clear_text, #warnings_container.clear #warning_btn_cancel, #warnings_container.clear #warning_btn_clear { display:inline-block; }'+
  876. '#warnings_container.warning_local_bookmark #warning_local_bookmark, #warnings_container.warning_local_bookmark #warning_btn_ok { display:inline-block; margin-left:auto; }'+
  877. '#warnings_container.warning_local_file #warning_local_file, #warnings_container.warning_local_file #warning_btn_ok { display:inline-block; margin-left:auto; }'+
  878. '#warnings_container.warning_close_font #warning_btn_ok { display:inline-block; margin-left:auto; }'+
  879. '#warnings_container.warning_close_font #warning_btn_cancel { display:inline-block; }'+
  880. '#warnings_container.warning_close_playlist #warning_btn_ok { display:inline-block; margin-left:auto; }'+
  881. '#warnings_container.warning_close_playlist #warning_btn_cancel { display:inline-block; }'+
  882. '#warning_make_playlist fieldset { margin:0; padding:0; border:0; }'+
  883. '#warning_make_playlist fieldset div { padding:0 0 2px; }'+
  884. '#warning_make_playlist .indent { text-indent:2em; }'+
  885. '#warning_make_playlist input { margin-right:6px; }'+
  886. '#warnings_container.warning_make_playlist #warning_make_playlist { display:flex; flex-direction:column; }'+
  887. '#warnings_container.warning_make_playlist #warning_btn_ok, #warnings_container.warning_make_playlist #warning_btn_cancel { display: inline-block; }' +
  888. '#warnings_container.warning_no_playlist #warning_no_playlist, #warnings_container.warning_no_playlist #warning_btn_ok { display: inline-block; }' +
  889. '#warnings_container.warning_local_playlist #warning_local_playlist, #warnings_container.warning_local_playlist #warning_btn_ok { display:inline-block; margin-left:auto; }'
  890. ;
  891. var $main_styles =
  892. ':root, html, body { margin:0; padding:0; width:100%; max-width:100%; height:100vh; overflow:hidden; border:0; border-radius:0; font-family:'+ $settings.UI_font +'; font-size:'+ $settings.UI_font_size +'; hyphens:auto; display:flex; }' +
  893. 'table { width:100%; border:0; border-collapse: collapse; }' +
  894. '#sidebar_wrapper li, #toolbar li { list-style:none; }' + //
  895. 'a, a:hover { text-decoration: none !important; }' +
  896. 'button { padding:2px 6px; border:solid 1px #333; border-radius:3px; cursor:pointer; height:18px; font-family:'+ $settings.UI_font +' !important; font-size:1em !important; line-height:0; }' +
  897. 'button:focus, textarea:focus, audio:focus { outline:none; }' +
  898.  
  899. '#main_content { width:100%; display:flex; flex-direction:row; overflow:hidden; }' + // table with initial height
  900. '#handle { position:absolute; top:0; bottom:0; z-index:1; cursor:col-resize; right:-4px; width:7px; }' +
  901. // OVERLAY
  902. 'body.has_overlay #handle { z-index:9999; }' +
  903. 'body.has_warning::before, body.has_overlay::before { content:""; position:absolute; top:0; right:0; bottom:0; left:0; z-index:9998; -webkit-user-select:none; -moz-user-select:none; user-select:none; }' +
  904. // WARNING STYLES
  905. $warning_styles +
  906. // HELP STYLES
  907. '#help_container { display:none; padding: 0 1em 1em; overflow:scroll; position:absolute; top:0; right:0; bottom:0; left:0; z-index:9998; contain:strict; }' + // tr
  908. '#help_container header { font-size:0.875rem; text-align:center; margin-right:calc(-1em - 2px); margin-left:calc(-1em - 2px); }' + //
  909. '#help_container header > span { display:inline-block; padding:6px; font-weight:bold; }' + //
  910. '#help_container tr { display:flex !important; border-bottom:solid 1px #666; }' + // td
  911. '#help_container td { vertical-align:top; }' + // td
  912. '#content_help { margin:1em auto; width:auto; overflow:auto; }' + // table
  913. '#close_help { float:right; margin-left:-100% !important; margin:4px 6px; }' + //
  914. '#content_help tbody { font-size:0.875rem; padding:8px; overflow:auto; }' + //
  915. '#content_help td.kbd_shortcut { text-align:right; width:33%; padding:4px 9px 4px 6px; }' + // td
  916. '#content_help td.help_description { width:66%; padding:4px 6px 4px 12px; }' + // td
  917. '#content_help kbd { display:inline-block; min-width:1em; margin:2px; padding:2px 6px; border:solid 1px #888; border-radius: 3px; text-align:center; font-family:inherit; font-size:0.875em; }' +
  918.  
  919. //***** SIDEBAR STYLES *****//
  920. '#sidebar_wrapper { min-width:220px; padding:0; position:relative; z-index:1; display:flex; flex-direction:column; }' +
  921. '#sidebar { overflow:hidden; font-size:0.875rem; display:flex; flex-direction:column; flex-basis:100%; }' +
  922. '#sidebar_header { font-size:0.875rem; position:relative; z-index:3; user-select:none; -webkit-user-select:none; display:flex; flex-direction:column; }' +
  923. '#sidebar_title { font-weight:normal; display:flex; flex-direction:row; }' +
  924. '#sidebar_title div { padding: 4px 6px; text-align:center; letter-spacing:0.5em; text-indent:0.75em; flex-basis:100%; }' +
  925. '#sidebar_header_body { display:flex; flex-direction:column; }' +
  926. '#toggle_sidebar { position:absolute; top:0; right:0; cursor:pointer; width:24px; height:21px; z-index:9997; background-image:'+ SVG_UI_Icon('toggle') +'; background-size:18px; background-position:center; }' +
  927. '#sidebar ul { -webkit-margin-before:0em !important; -webkit-margin-after:0em !important; -webkit-padding-start:0em; }' +
  928. '#sidebar_menus { cursor:pointer; display:flex; flex-direction:row; }' +
  929. // PARENT MENU
  930. '#parent_dir_menu { flex-basis:24px; max-width:24px; min-width:24px; padding:0; position:relative; }' +
  931. '#parent_dir_nav { margin:0; padding:0; display:block; position:absolute; top:0; right:0; bottom:0; left:0; }' +
  932. '#parent_dir_nav a { height:100%; padding:0; text-align:center; background-position:center; background-repeat:no-repeat; background-size:12px; }' +
  933. // PARENTS MENU
  934. '#parents_dir_menu { padding:0; flex-grow:1; }' +
  935. '#parents_dir_nav { margin:0; padding:0; display:block; line-height:1.4; }' + // nav
  936. '#current_dir_path { cursor:pointer; font-weight:bold; hyphens:none; padding:4px 6px 4px 6px; text-align:center; word-break:break-word; z-index:9998; }' +
  937. '#parents_links { margin:0; padding:0; display:none; position:absolute; right:0; left:0; box-shadow: 0px 4px 6px -3px #333; z-index:9998; }' + // ul
  938. '#parents_links a { margin:0; padding:4px 8px; display:block; }' +
  939. // MAIN MENU
  940. '#menu_container { display:flex; flex-direction:column; justify-content:center; flex-basis:24px; max-width:24px; min-width:24px; padding:0; }' +
  941. '#menu_nav { margin:0; padding:0; display:block; cursor:pointer; }' + // nav
  942. '#menu_nav div { width:24px; background-image:'+ SVG_UI_Icon('menu') + '; background-position:center; background-repeat:no-repeat; background-size:13px; }' +
  943. '#menu { display:none; margin:0; padding:0; position:absolute; right:0;left:0; zindex:9998; box-shadow: 0px 4px 6px -3px #333; z-index:9998; }' + // ul
  944. '#menu li.has_submenu { position:relative; background-position:right 6px center; background-repeat:no-repeat; background-size:12px; }' + // right triangle icon
  945. '#menu li.bookmark a::before { content:""; width:24px; height: 12px; background-size:12px; }' + // bookmark icon
  946. '.submenu { display:none; margin:0; padding:0; position:absolute; top:-1px !important; left:100%; right:0; width:100%; box-shadow:0px 4px 6px -3px #333; width:100%; max-width: 240px; }' + // ul
  947. '#menu ul.submenu li a { margin:0; padding:6px 8px 6px 0; }' +
  948. '#menu input { width:0; float:left; }' +
  949. '.menu_item { margin:0; padding:6px 8px 6px 0; display:flex; }' +
  950. '.menu_item::before { content:""; width:24px; height:9px; margin:2px 0 -2px; display:inline-block; background-position:center; background-repeat:no-repeat; }' +
  951. // SIDEBAR BUTTONS
  952. '#sidebar_buttons { position:relative; display:flex; flex-direction:row; }' +
  953. '#sidebar_buttons_left { padding:6px; }' +
  954. '#show_details { margin-top:0; margin-right:0.5em; padding:0 4px 0 4px; }' +
  955. '#show::before { content:"Show "; }' +
  956. '#show_invisibles_container { cursor:pointer; display:inline-flex; vertical-align:text-top; padding:0; height:100%; }' +
  957. '#show_invisibles { cursor:pointer; margin:0 4px 0 0; }' +
  958. '#show_invisibles:hover span { font-weight:bold; }' +
  959. // Grid Btn
  960. '#grid_btn { display:none; margin:0 0 0 auto; padding:0; width:24px; position:relative; z-index:9997; background-color:inherit; background-position:center; background-size:14px; cursor:pointer; outline:none; }' +
  961. '#grid_btn ul { display:none; margin:0; padding:0; padding-right:24px; padding-left:0px; position:absolute; top:-1px; right:-1px; background-position:right 5px top 8px; background-repeat:no-repeat; background-size:14px; box-shadow:0px 4px 6px -3px #333; }' +
  962. '#grid_btn ul li { padding:4px 6px; text-align:right; width:100%; white-space:pre; box-sizing:border-box; }' +
  963. // SORTING ITEMS
  964. '#sorting_row_1 { display:flex; cursor:pointer; justify-content:space-between; flex-direction:row; }' +
  965. '#sorting_row_2 { display:none; cursor:pointer; justify-content:space-between; flex-direction:row; }' +
  966. '#sorting_row_1 div { width:50%; }' +
  967. '#sorting_row_2 div { width:25%; }' +
  968. '#sorting_row_1 div, #sorting_row_2 div { flex-grow:1; padding:0; }' +
  969. '#sorting .sorting { white-space:pre; }' +
  970. '#sorting_row_1 span { display:inline-block; padding:6px 0; }' +
  971. '#sorting_row_2 span { display:inline-block; padding:0 0 6px 0; }' +
  972. '.sorting:hover span { font-weight:bold; }' + //
  973. '.sorting span::before, .sorting span::after { content:""; width:16px; height:8px; display:inline-block; position:relative; color:#CCC; background-position:center; background-repeat:no-repeat; background-size:10px; }' +// content
  974. '.sorting.down span::after { transform:rotate(180deg) }' +
  975. '#sort_by_name input { display:none; margin:0 4px 0 2px; }' +
  976. '#sort_by_name, #sort_by_size { text-align:left; }' +
  977. '#sort_by_date, #sort_by_kind { text-align:center; }' +
  978. '#sort_by_default, #sort_by_ext { text-align:right; }' +
  979. '#sorting div.up span::after { transform:rotate(180deg) !important; }' +
  980. // TEXT EDITOR ROW
  981. '#text_editor_row { display:none; }' +
  982. '#text_editor_row a { padding:6px 0 6px 16px; text-align:left; font-weight:bold; }' +
  983.  
  984. //***** DIR_LIST STYLES *****//
  985. '#dir_list_wrapper { overflow:scroll; position:relative; }' +
  986. '#dir_list { overflow:hidden; position:relative; font-size:0.875rem; }' +
  987. '#tbody { counter-reset:row; height:100%; overflow:hidden; transition:opacity .125s; background-position:center; background-repeat:no-repeat; background-size:50%; }' +
  988. '#tbody > tr { margin-inline-start:0; display:none; grid-gap:0; grid-template-columns:minmax(auto,5rem) 1fr minmax(auto,7em); }' + // tr
  989. '.tbody_row_cell { padding:0; }' + // td
  990. '.tbody_row_cell_name { grid-column: 1 / span 3; font-variant-numeric:tabular-nums; }' +
  991. '.tbody_row_cell_name_a { display:flex; text-decoration:none; -webkit-padding-start:0; -moz-padding-start:0; }' + // td.name a.icon
  992. '.tbody_row_cell_name_a::before { display:none; counter-increment:row; content:counter(row); width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; text-align:right; padding-right:1px; }' + // numbers
  993. '.tbody_row_cell_name_a_span { display:flex; line-height:1.4; text-align:left; word-break:break-word; }' + // req: .has_background_before
  994. '.tbody_row_cell_name_a_span input { display:none; margin:2px 6px 0 0; }' + // media checkboxes
  995. '.tbody_row_cell_details { display:none; text-align:right; white-space:pre; max-height:1em; font-variant-numeric:tabular-nums; }' +
  996. '.tbody_row_cell_details.size { grid-column: 1; grid-row: 2; padding:0 8px 3px 8px; }' +
  997. '.tbody_row_cell_details.date { grid-column: 2; grid-row: 2; text-align:right; overflow:hidden; overflow-wrap:break-word; text-overflow:""; padding-right:8px; }' +
  998. '.tbody_row_cell_details.date span { white-space:pre; display:inline-block; }' +
  999. '.tbody_row_cell_details.kind { grid-column: 3; grid-row: 2; overflow:hidden; text-overflow:ellipsis; }' +
  1000. '.tbody_row_cell_name_a { padding:5px 8px 5px 0; }' + //
  1001. '.tbody_row_cell_name_a::before { margin-top:1px; }' + //
  1002. '.tbody_row_cell_details { padding:0 16px 3px 0; }' + // padding:4px 8px 4px 0;
  1003. 'tr:hover a, tr.selected a, tr.playing a { font-weight:bold; }' +
  1004. 'tr.disabled, tr.disabled a, tr.disabled span, tr.ignored, tr.ignored a { cursor:not-allowed; }' +
  1005. // PLAYLIST ITEMS
  1006. 'body[class$="list"] #tbody tr { display: grid; grid-gap:0; grid-template-columns: 48px minmax(8rem,18rem) minmax(auto,8rem); }' +
  1007. 'body[class$="list"] #tbody td.name { grid-column: 1 / span 4; }' +
  1008. 'body[class$="list"] #tbody td.size { grid-column: 1 / span 2; grid-row: 2; padding:0 8px 6px 8px; text-align:left; text-indent: 40px; }' +
  1009. 'body[class$="list"] tbody td.kind { grid-column: 3 / span 2; grid-row: 2; padding:0 8px 6px 8px; }' +
  1010. 'body[class$="list"] #tbody td.details.ext { display:none; }' +
  1011. 'body.show_numbers[class$="list"] #tbody td.size { text-indent: 48px; }' +
  1012. //***** SIDEBAR FOOTER *****//
  1013. '#tfoot { margin-top:auto; padding:0; position:relative; font-size:0.875rem; user-select:none; -webkit-user-select:none; display:flex; flex-direction:column; }' +
  1014. '#tfoot:hover { box-shadow: 0px 4px 6px 3px #333; }' + // tr
  1015. // STATS
  1016. '#stats { cursor:pointer; float:left; width:100%; overflow:hidden; flex-direction:column; line-height:1.6; font-size:0.875rem; }' + // td
  1017. '#stats_summary, #stats_summary_playlist_container { padding:2px 8px; overflow:hidden; white-space:pre; }' + // tr
  1018. '#stats_summary div > div { padding:0 6px; }' + // tr
  1019. '#stats_summary_playlist_container, #stats_summary_detailed_container, #stats_details_container { display:none; flex-direction:column; }' + // tbody
  1020. '#stats_summary_playlist_container > div, #stats_summary_detailed_container > div, #stats_details_container > div { flex-direction:row; }' + // tbody
  1021. '.summary_detailed { display:none; }' +
  1022. '.stats_count { display:inline-block; padding:2px 2px 2px 6px; text-align:right; min-width: 22px; }' +
  1023. '.stats_kind { display:inline-block; padding:2px 6px 2px 0; }' +
  1024. '#stats_summary_detailed_total { padding-left:4px; font-weight:bold; }' +
  1025. '#stats_summary_detailed_dirs span { padding-bottom:0; }' +
  1026. '#stats_summary_detailed_dirs .stats_kind::before { background-image:'+ SVG_UI_File_Icon('file_icon_dir_default') +'; }' +
  1027. '#stats_summary_detailed_files .stats_kind::before { background-image:'+ SVG_UI_File_Icon('file_icon_file_default') +'; }' +
  1028. '#stats_details_container > div:first-of-type span { padding-top:4px; }' +
  1029. '#stats_details_container > div:last-of-type span { padding-bottom:4px; }' +
  1030. '#stats_summary_detailed_dirs .stats_kind, #stats_summary_detailed_files .stats_kind { display:flex; }' +
  1031. '#stats_summary_detailed_dirs:hover, #stats_summary_detailed_files:hover, #stats_details_container div:hover { font-weight:bold; }' +
  1032. // SIDEBAR FOOTER LINKS
  1033. '#footer_links { margin-top:-1px; position:absolute; z-index:1; right:0; cursor:pointer; float:right; width:24px; height:100%; background-image:'+ SVG_UI_Icon('toggle') +'; transform:rotate(180deg) !important; background-size:18px; background-position:2px center; }' + // td
  1034. '#footer_links ul { display:none; padding:0; position:absolute; top:calc(100% - 1px); right:unset; left:calc(100% - 27px); white-space:pre; box-shadow:-0px -3px 6px -3px #333; transform:rotate(180deg) !important; }' + // ul
  1035. '#open_in_content_pane { padding:4px 6px; text-align:right; }' +
  1036. '#view_directory_source { padding:4px 6px; text-align:right; }' +
  1037.  
  1038. // CLASSES AND ELEMENTS
  1039. '.has_icon_before::before { content:""; display:block; float:left; width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; margin-top:1px; background-position:center; background-repeat:no-repeat; background-size:14px; }' +
  1040. '.has_checkmark_before::before { content:""; height:9px; background-position:center; background-repeat:no-repeat; }' +
  1041. 'ul.has_popout_menu, .editor_theme_light #toolbar ul.has_popout_menu { background-color:#C0C0C0; border:solid 1px #666; }' +
  1042. 'ul.has_popout_menu li { background-color:#D0D0D0; }' +
  1043. 'ul.has_popout_menu li:hover { background-color:#E0E0E0; }' +
  1044. '.theme_dark #sidebar_wrapper ul.has_popout_menu { border:solid 1px #111; }' +
  1045. '.theme_dark #sidebar_wrapper ul.has_popout_menu, .theme_dark #sidebar_wrapper ul.has_popout_menu li { background-color:#505050; }' +
  1046. '.editor_theme_dark #toolbar ul.has_popout_menu li { background-color:#C0C0C0; }' +
  1047. '.theme_dark #sidebar_wrapper ul.has_popout_menu li:hover { background-color:#686868; }' +
  1048. //***** END SIDEBAR STYLES *****//
  1049.  
  1050. //***** CONTENT STYLES *****//
  1051. '#content_pane { height:100%; padding:0; position:relative; transform:scale(1); vertical-align:top; display:flex; flex-direction:column; contain:strict; }' + // td
  1052. '#content_header { display:block; font-size:0.875rem; position:relative; z-index:3; }' + // header
  1053. '#content_header table { font-size:0.875rem; z-index:2; }' +
  1054. '#content_title { text-align:center; display:flex; flex-direction:row; justify-content:space-between; }' + // tr
  1055. // CONTENT TITLE BUTTONS LEFT
  1056. '#title_buttons_left { padding:4px 6px; text-align:left; width:4rem; max-width:9.5em; vertical-align:top; }' + // td
  1057. '#reload_btn { float:left; width:52px; }' +
  1058. '#reload_btn::before { content:"Reload"; }' +
  1059. '#prev_next_btns { margin-left:8px; cursor:pointer; float:left; line-height:1; padding:0; position:relative; }' + // button
  1060. '#prev_next_btns span { width:2em; height:18px; }' +
  1061. '#prev_btn, #next_btn { background-position:center 36%; background-repeat:no-repeat; background-size:33%; }' +
  1062. '#prev_btn { background-image:'+ SVG_UI_Icon('chevron_left') + '; }' +
  1063. '#next_btn { background-image:'+ SVG_UI_Icon('chevron_right') + '; }' +
  1064. // CONTENT TITLE
  1065. '#title { line-height:1.4; min-width:10em; min-height:18px; padding:4px 8px; text-align:center; word-break:break-word; vertical-align:top; cursor:pointer; }' + // td
  1066. '#title span { font-weight:bold; hyphens:none; }' +
  1067. '#title span::before { content:""; margin-top:1px; margin-bottom:-2px; width:24px; height:14px; display:inline-block; background-position:center; background-repeat:no-repeat; font-weight:normal; }' +
  1068. '#title span::after { font-weight:bold; }' +
  1069. '#content_pane[data-content="has_image"] #title span::after { font-weight:normal; }' +
  1070. // CONTENT TITLE BUTTONS RIGHT
  1071. '#title_buttons_right { padding:4px 6px; text-align:right; width:4rem; max-width:9.5em; vertical-align:top; }' + // td
  1072. '#scale { cursor:pointer; float:right; line-height:1; margin-right:8px; padding:0 4px; position:relative; background-color:#FFF; }' + // button
  1073. '#scale span { width:2em; height:18px; background-position:center 36%; background-repeat:no-repeat; background-size:10px; }' +
  1074. '#decrease { margin-left:-4px; background-image:'+ SVG_UI_Icon('minus') +'; }' +
  1075. '#increase { margin-right:-4px; background-image:'+ SVG_UI_Icon('plus') +'; }' +
  1076. '#close_btn { float:right; padding:0px; width:52px; }' + // close button
  1077. '#close_btn::before { content:"Close"; }' + //
  1078. '.split_btn { display:none; }' +
  1079. '.split_btn span { display:inline-flex; }' +
  1080. '.split_btn::after { content:""; position: absolute; top: 0; bottom: 0; left: 50%; }' +
  1081. // CONTENT AUDIO TITLE
  1082. '#content_audio_title { display:none;}' + // tr
  1083. '#content_audio_title span { display:block; width:100%; cursor:pointer; padding:4px 6px 6px; text-align:center; line-height:1.4; }' + // td
  1084. '#content_audio_title span::before { content:""; padding-right:18px; font-weight:normal; background-position:center; background-position:right 4px center; background-repeat:no-repeat; }' + // td
  1085. // CONTENT AUDIO PLAYER
  1086. '#content_audio { display:none; justify-content:center; padding-bottom:6px; }' + // tr
  1087. '#content_audio > div > div { text-align:center; font-weight:bold;}' + // td
  1088. '#audio_container { padding:0 4px; height:32px; display:flex; background-color:rgb(241, 243, 244); flex-direction:row; }' + // div; background-color is for chrome browsers
  1089. '#prev_track, #next_track { width:2rem; padding:0; display:inline-block; overflow:auto; background-image:'+ SVG_UI_Icon('prev_next_track') +'; background-position:center; background-repeat:no-repeat; }' + // div
  1090. '#prev_track { transform:rotate(180deg) !important; }' + // div
  1091. '#audio { height:32px; }' + // audio
  1092. // 'body:not(.is_gecko) #audio { margin-top: -9px; }' + // hacky fix for PIP button in Vivaldi
  1093. '#close_audio { width:2rem; padding:0; position:relative; display:inline-block; background-image:'+ SVG_UI_Icon('multiply') +'; background-position:center; background-repeat:no-repeat; background-size:14px; }' + // div , .audio_controls, #glyph_viewer_info div
  1094. '#audio_options { margin-top:0; margin-right:calc(-6em - 8px); padding:0 4px; width:6em; display:flex; flex-direction:column; justify-content:center; }' + // div
  1095. '#loop_label input { margin:0px 4px 2px}' + // input
  1096. '#shuffle_label input { margin:2px 4px 0px}' + // input
  1097. '#audio_iframe { margin:0; padding:0; border:0; }' +
  1098. // CONTENT TITLE PLAYLIST ENTRY (#content_playlist and #content_audio_playlist)
  1099. '.playlist_entry_container { display:none; padding:4px 6px; text-align:center; flex-direction:row; }' + // td
  1100. '.playlist_entry_container textarea { width:100%; padding:0 6px; border:0; resize:vertical; }' + // tr
  1101.  
  1102. // CONTENT_CONTAINER (section)
  1103. '#content_container { box-sizing:border-box; justify-content:center; padding:0; position:relative; bottom:0; overflow:auto; width:100%; background-position:center; background-repeat:no-repeat; background-size: 50%; display:flex; flex-basis:100%; contain:strict; }' + // section
  1104. '.content { display:none; overflow:scroll; width:100%; height:100% }' + // hide content by default
  1105. // CONTENT GRID (div)
  1106. '#content_grid { display:none; position:absolute; padding:0; width:100%; font-size:1rem; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax('+ ( $settings.grid_image_size + 16) +'px, auto)); grid-auto-rows:minmax(min-content, max-content); }' +
  1107. '#content_grid::after { content:""; width:1px; position:absolute; top:0; right:0; bottom:0; }' +
  1108. '#content_grid a { display:block; }' +
  1109. // Image Grid Items
  1110. '.image_grid_item { padding:6px; grid-column:auto; display:flex; align-items:center; justify-content:center; line-height:0; }' +
  1111. '.image_grid_item img { width:auto; max-width:'+ ($settings.grid_image_size).toString() +'px; max-height:'+ ($settings.grid_image_size) +'px; position:relative; }' +
  1112. '.image_grid_item img[src$=".svg"] { width: 100%; height:100%; }' +
  1113. // Font Grid Items
  1114. '.font_grid_item { line-height:1; padding:8px 20px; grid-column: 1 / -1; }' +
  1115. '.font_grid_item p { margin:0; padding:0 0 6px 0; line-height:1; font-size:1rem; letter-spacing:0.1em; text-indent:0.1em; }' +
  1116. '.font_grid_item h2 { margin:0; font-weight:normal; font-size:'+ $settings.grid_font_size * 4 +'em; }' +
  1117. '.image_grid_item + .font_grid_item { margin-top:-1px; }' + // mask double borders
  1118. // CONTENT TEXT EDITOR
  1119. '#content_text { display:none; width:100%; max-width:100%; height:100%; overflow:hidden; padding:0; position:absolute; z-index:1; flex-direction:column; }' + // div
  1120. // CONTENT FONT.content
  1121. '#content_font { hyphens:none; padding:0; position:relative; font-size:'+ $settings.grid_font_size +'em; overflow-wrap:break-word; }' + // div
  1122. '#font_specimen { max-width:100%; display:none; line-height:1.2; flex-direction:column; }' + // div
  1123. '#specimen { padding: 20px; font-size:4em; word-break:break-all; line-height:1.2; }' + // div
  1124. '#specimen_2 { margin:0; font-weight:normal; font-size:8em; overflow:hidden; text-overflow:ellipsis; white-space:pre; }' +
  1125. '#specimen_2H4 { margin:0; font-weight:normal; font-size:1.618em; overflow:hidden; text-overflow:ellipsis; }' +
  1126. '#specimen_3 { margin:0; font-weight:normal; font-size:6em; text-align:justify; overflow:hidden; text-overflow:ellipsis; white-space:pre; }' +
  1127. '#specimen_3H3 { margin:0; font-size:2em; hyphens:auto; }' +
  1128. '#specimen_string_2 { padding:20px; text-align:justify; hyphens:auto; }' + // div
  1129. '#specimen_string_3 { padding:20px; text-align:justify; }' + // div
  1130. '.lorem { text-align:justify; hyphens:auto; font-size:1em; line-height:1.4; column-gap:1.5em; overflow-wrap:normal; word-break:normal; }' + // div
  1131. '#lorem { padding:20px 20px 0; }' + // div
  1132. '#lorem::first-line { letter-spacing:0.1em; text-indent:0.1em; font-size:'+ $settings.grid_font_size * 1.33 +'em; font-variant:small-caps; }' +
  1133. '#lorem_2 { padding:12px 20px 0; columns:2; }' + // div
  1134. '#lorem_3 { padding:12px 20px 40px; columns:3; }' + // div
  1135. // FONT GLYPHS
  1136. '#font_viewer { display:none; position:relative; font-family:unset; width:100%; }' + // div
  1137. '#content_pane[data-content="has_font_file"] #font_viewer { overflow-y:auto; }' + // div
  1138. '#content_pane[data-content="has_glyph"] #font_viewer { overflow:auto; }' +
  1139. '#content_pane[data-content="has_glyph"] #glyphs_container { visibility:hidden; }' +
  1140. '#glyphs_container { padding:0; position:relative; text-align:center; display:grid; grid-gap:0; grid-template-columns:repeat(auto-fill, minmax(120px,auto)); }' + // div
  1141. '.glyph_container { padding:0; position:relative; }' + // div
  1142. '.glyph_info { padding:2px; position:absolute; right:0; bottom:0; left:0; font-size:0.75rem; }' +
  1143. '#glyph_viewer { display:none; padding:0; z-index:1; position:absolute; top:0; right:0; bottom:0; left:0; background-color:#FFF; background-position:center; background-repeat:no-repeat; background-size:contain; }' + // div
  1144. '#glyph_viewer_info { padding:4px 6px; position:fixed; right:0; left:0; text-align:center; height:18px; line-height:1.6; font-size:0.875rem; }' + // div
  1145. '#content_font svg { width: 100%; }' +
  1146. '#save_svg_hidden { float:left; visibility:hidden; }' + // button
  1147. '#glyph_viewer_info div { padding:0; display:inline-block; }' + // button
  1148. '#glyph_viewer_info div::before { content:"Glyph "; }' +
  1149. '#save_svg { float:right; }' + // button
  1150. '#font_info { max-height:'+ (window.innerHeight * 0.75) +'px; font-size:0.875rem; position:fixed; bottom:0; z-index:2; }' +
  1151. '#font_info:hover { box-shadow:0px 4px 6px 3px #333; }' + // tr
  1152. '#font_info th { padding:4px 6px 5px; letter-spacing:0.1em; text-indent:0.1em; }' +
  1153. '#font_info_body { display:none; max-height:'+ ((window.innerHeight * 0.75) - 64) +'px; font-size:0.875rem; overflow:auto; }' +
  1154. '.font_info_name { padding:4px 6px; text-align:right; font-weight:bold; width:33%; }' +
  1155. '.font_info_value { padding:4px 6px; width_66%; }' +
  1156. '.font_info_value a { font-weight:bold; }' +
  1157. // CONTENT IMAGE.content
  1158. '#content_image_container { display:none; margin:0; padding:2rem 2.5rem; position:relative; overflow:auto; box-sizing:border-box; }' + // div
  1159. '#content_image { margin:auto; width:auto; max-width:100%; max-height:100%; position:relative; object-fit:contain; cursor:zoom-in; }' + // img
  1160. '#content_pane.has_zoom_image #content_image_container { padding:0; }' + // div
  1161. // OTHER CONTENT ELEMENTS
  1162. '#content_pdf { height:100%; padding:0; position:relative; width:100%; }' + // embed.content
  1163. '#content_video { padding:0; position:absolute; background:transparent; }' + // video.content
  1164. '#content_iframe { display:none; width:100%; height:100%; padding:0; position:relative; background:white; border:0; }' // iframe.content
  1165. ;
  1166. var $color_and_background_styles = // added to #top and #iframe_body
  1167. // BACKGROUND COLORS
  1168. // 'body.theme_light .background_color_A0_20 { background-color: #A0A0A0; }' +
  1169. // 'body.theme_dark .background_color_A0_20 { background-color: #202020; }' +
  1170. 'body.theme_light, body.theme_light .background_color_B0_30 { background-color: #B0B0B0; }' +
  1171. 'body.theme_dark, body.theme_dark .background_color_B0_30 { background-color: #303030; }' +
  1172. 'body.theme_light .background_color_C0_40 { background-color: #C0C0C0; }' +
  1173. 'body.theme_dark .background_color_C0_40 { background-color: #404040; }' +
  1174. 'body.theme_light .background_color_D0_50 { background-color: #D0D0D0; }' +
  1175. 'body.theme_dark .background_color_D0_50 { background-color: #505050; }' +
  1176. 'body.theme_light .background_color_E0_60 { background-color: #E0E0E0; }' +
  1177. 'body.theme_dark .background_color_E0_60 { background-color: #606060; }' +
  1178.  
  1179. 'body.theme_light .background_color_DD_44 { background-color: #DDDDDD; }' + // tbody background
  1180. 'body.theme_dark .background_color_DD_44 { background-color: #383838; }' +
  1181. 'body.theme_light:not(.editor_theme_dark) .background_color_DD_33, body.editor_theme_light .background_color_DD_33 { background-color: #DDDDDD; }' +
  1182. 'body.theme_dark:not(.editor_theme_light) .background_color_DD_33, body.editor_theme_dark .background_color_DD_33 { background-color: #333333; }' +
  1183. 'body.theme_light .background_color_EE_22, body.editor_theme_light .background_color_DD_33:focus, body.theme_light #content_container:hover, body.theme_light #content_grid::after { background-color: #EEEEEE; }' +
  1184. 'body.theme_dark .background_color_EE_22, body.editor_theme_dark .background_color_DD_33:focus, body.theme_dark #content_container:hover, body.theme_dark #content_grid::after { background-color: #222222; }' +
  1185. 'body.theme_light .background_color_FF_11 { background-color: #EFEFEF; }' +
  1186. 'body.theme_dark .background_color_FF_11 { background-color: #0F0F0F; }' +
  1187. 'body.theme_light .background_color_EE_22:hover, body.theme_light .background_color_EE_22.hovered, body.theme_light .background_color_EE_22.selected { background-color: #FFFFFF; }' +
  1188. 'body.theme_dark .background_color_EE_22:hover, body.theme_dark .background_color_EE_22.hovered, body.theme_dark .background_color_EE_22.selected { background-color: #000000; }' +
  1189. 'body.theme_light .background_color_22_EE { background-color: #222222; }' +
  1190. 'body.theme_dark .background_color_22_EE { background-color: #EEEEEE; }' +
  1191. 'body.theme_light .background_color_11_FF, body.theme_light .background_color_22_EE:hover, body.theme_light .background_color_22_EE.hovered { background-color: #0F0F0F; }' +
  1192. 'body.theme_dark .background_color_11_FF, body.theme_dark .background_color_22_EE:hover, body.theme_dark .background_color_22_EE.hovered { background-color: #EFEFEF; }' +
  1193.  
  1194. // DIR LIST ROWS: .alternate_background
  1195. 'body.theme_dark.alternate_background #tbody tr:nth-of-type(odd) { background-color: #505050; }' +
  1196. 'body.theme_light.alternate_background #tbody tr:nth-of-type(odd) { background-color: #D0D0D0; }' +
  1197. 'body.theme_dark.alternate_background #tbody tr:nth-of-type(even) { background-color: #404040; }' +
  1198. 'body.theme_light.alternate_background #tbody tr:nth-of-type(even) { background-color: #E8E8E8; }' +
  1199. 'body.theme_dark #content_help tr:nth-of-type(even) { background-color: #484848; }' +
  1200. 'body.theme_light #content_help tr:nth-of-type(even) { background-color: #CCCCCC; }' +
  1201. // NON-MEDIA ROWS .selected, .loaded, :hover ("light cyan")
  1202. 'body.theme_light tr:not(.media).selected, body.theme_light tr:not(.media).selected.loaded, body.theme_light tr.selected.loaded:hover, body.theme_light #menu li.selected'+
  1203. '{ background-color: rgba(172,202,235,1.00) !important; }' +
  1204. 'body.theme_light tr:not(.media).loaded, body.theme_light #tbody tr:not(.media):hover, body.theme_light.alternate_background #tbody tr:not(.media):hover, body.theme_light tr:not(.media).hovered'+
  1205. '{ background-color: rgba(172,202,235,0.60) !important; }' +
  1206. 'body.theme_dark tr:not(.media).selected, body.theme_dark tr:not(.media).selected.loaded, body.theme_dark tr.selected.loaded:hover, body.theme_dark #menu li.selected'+
  1207. '{ background-color: rgba(101,140,179,0.80) !important; }' + //#658CB3
  1208. 'body.theme_dark tr:not(.media).loaded, body.theme_dark #tbody tr:not(.media):hover, body.theme_dark.alternate_background #tbody tr:not(.media):hover, body.theme_dark tr:not(.media).hovered'+
  1209. '{ background-color: rgba(101,140,179,0.60) !important; }' +
  1210. // MEDIA ROWS .playing, .selected, :hover
  1211. 'body.theme_light tr.media.playing { background-color: rgba(130,196,196,1) !important; }' + // #82C4C4
  1212. 'body.theme_light tr.media.selected:not(.playing), body.theme_light tr.media.hovered' +
  1213. '{ background-color: rgba(116,190,190,0.60) !important; }' +
  1214. 'body.theme_light tr.media:not(.playing):hover { background-color: rgba(116,190,190,0.40) !important; }' +
  1215. 'body.theme_dark tr.media.playing { background-color: rgba(076,143,143,0.75) !important; }' +
  1216. 'body.theme_dark tr.media.selected:not(.playing), body.theme_dark tr.media.hovered' +
  1217. '{ background-color: rgba(076,143,143,0.55) !important; }' +
  1218. 'body.theme_dark tr.media:hover { background-color: rgba(076,143,143,0.45) !important; }' +
  1219. // text editor row ("purple")
  1220. 'body.theme_light #text_editor_row.has_text_editor, body.theme_light.edited #text_editor_row { background-color: rgba(160,160,230,1.00); }' + // #A0A0E6
  1221. 'body.theme_dark #text_editor_row.has_text_editor, body.theme_dark.edited #text_editor_row { background-color: rgba(100,100,160,1.00); }' + // #6464A0
  1222. // menu items :hover, with exceptions for default text editor view
  1223. 'body.theme_dark .menu li:hover, body.theme_light .menu li#preview_text_menu_item span:hover { background-color: #686868; }' +
  1224. 'body.theme_light .menu li:hover, body.theme_light .menu li#preview_text_menu_item span:hover { background-color: #B8B8B8; }' +
  1225. 'body.theme_light .menu li#preview_text_menu_item:hover, body.theme_light .menu li#preview_text_menu_item:hover { background-color: initial; }' +
  1226. // BACKGROUND IMAGES
  1227. '.has_background, .has_background_before::before, .has_background_after::after { background-repeat:no-repeat; background-position:center; background-color:transparent !important; }' +
  1228. 'body.theme_light .bookmark > a::before { background-image: '+ SVG_UI_Icon('bookmark') +'; }' +
  1229. 'body.theme_dark .bookmark > a::before { background-image: '+ SVG_UI_Icon('bookmark_dark') +'; }' +
  1230. 'body.theme_light li.has_submenu { background-image: '+ SVG_UI_Icon('arrow') +'; }' +
  1231. 'body.theme_dark li.has_submenu { background-image: '+ SVG_UI_Icon('arrow_dark') +'; }' +
  1232. '.background_color_check_mark::before, #theme::before, body.sort_by_default #default span::before, body.sort_by_name #name span::before, body.sort_by_size #size span::before, body.sort_by_date #date span::before, body.sort_by_kind #kind span::before, body.sort_by_ext #ext span::before, body.alternate_background #alternate_background span::before, body.show_numbers #show_numbers span::before, body.autoload_media #autoload_media span::before, body.split_view #split_view_menu_item, body.split_view #split_view span::before, body.source_text #source_text::before, body.preview_text #preview_text::before, body.sort_by_default #sort_by_default span::before, body.sort_by_name #sort_by_name span::before, body.sort_by_size #sort_by_size span::before, body.sort_by_date #sort_by_date span::before, body.sort_by_kind #sort_by_kind span::before, body.sort_by_ext #sort_by_ext span::before, body.hide_ignored_items #hide_ignored_items span::before, body.ignore_ignored_items #ignore_ignored_items span::before, #disable_text_editing::before'+
  1233. '{ background-image:'+ SVG_UI_Icon('check_mark') +'; }' +
  1234. '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'+
  1235. '{ background-image:'+ SVG_UI_Icon('chevron_down') +'; }' +
  1236. '#parent_dir_nav a { background-image:'+ SVG_UI_Icon('chevron_up') +'; }' +
  1237. 'body.is_error #tbody { background-image: '+ SVG_UI_Icon('error') +'; }' +
  1238. 'body.theme_light #content_pane[data-content="has_ignored"] #content_container'+
  1239. '{ background-image:'+ SVG_UI_Icon('ignored') +'; background-size:25%; }' +
  1240. 'body.theme_dark #content_pane[data-content="has_ignored"] #content_container'+
  1241. '{ background-image:'+ SVG_UI_Icon('ignored_dark') +'; background-size:25%; }' +
  1242. 'body.has_audio #content_pane:not([data-content^="has_"]):not([data-loaded="unloaded"]) #content_container'+
  1243. '{ background-image:'+ SVG_UI_Icon('music') +'; }' +
  1244. CSS_UI_Icon_Rules() +
  1245. 'body.theme_light #grid_btn:not(.has_grid), body.theme_light #grid_btn:not(.has_grid) .menu'+
  1246. '{ background-image:'+ SVG_UI_Icon('grid') +'; }' +
  1247. 'body.theme_dark #grid_btn:not(.has_grid), body.theme_dark #grid_btn:not(.has_grid) .menu'+
  1248. '{ background-image:'+ SVG_UI_Icon('grid_dark') +'; }' +
  1249. 'body.theme_light #grid_btn.has_grid, body.theme_light #grid_btn.has_grid .menu'+
  1250. '{ background-image:'+ SVG_UI_Icon('grid_loaded') +'; }' +
  1251. 'body.theme_dark #grid_btn.has_grid, body.theme_dark #grid_btn.has_grid .menu'+
  1252. '{ background-image:'+ SVG_UI_Icon('grid_loaded_dark') +'; }' +
  1253. '#content_pane[data-content="has_text_editor"] #title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_markdown') +'; }' +
  1254. '#content_pane[data-content="has_font_file"] #title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_font') +'; }' +
  1255. '#content_audio_title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_audio') +'; height:14px !important; }' +
  1256. '#content_pane[data-content="has_grid"] #title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_dir') +'; height:14px !important; }' +
  1257. '#content_pane[data-content="has_view_directory_source"] #title span::before { background-image: '+ SVG_UI_File_Icon('file_icon_dir_default') +'; height:14px !important; background-size:contain; }' +
  1258. // BORDERS
  1259. 'body.theme_dark .border_all { border: solid 1px #111; }' +
  1260. 'body.theme_light .border_all { border: solid 1px #666; }' +
  1261. 'body.theme_dark .border_top { border-top: solid 1px #111; }' +
  1262. 'body.theme_light .border_top { border-top: solid 1px #666; }' +
  1263. 'body.theme_dark .border_right { border-right: solid 1px #111; }' +
  1264. 'body.theme_light .border_right { border-right: solid 1px #666; }' +
  1265. 'body.theme_dark .border_bottom, body.theme_dark #tbody tr:last-of-type:not(.invisible) { border-bottom: solid 1px #111; }' +
  1266. 'body.theme_light .border_bottom, body.theme_light #tbody tr:not(.invisible):last-of-type { border-bottom: solid 1px #666; }' +
  1267. 'body.theme_dark .border_left { border-left: solid 1px #111; }' +
  1268. 'body.theme_light .border_left { border-left: solid 1px #666; }' +
  1269. '.border_top_x { border-top: solid 1px #666; }' + // "x" = inverted for theme_dark
  1270. '.border_right_x { border-right: solid 1px #666; }' +
  1271. '.border_bottom_x { border-bottom: solid 1px #666; }' +
  1272. '.border_left_x { border-left: solid 1px #666; }' +
  1273. '.split_btn::after { border-left: solid 1px #333; }' +
  1274. // TEXT COLORS
  1275. 'body.theme_light a.text_color_111:hover, body.theme_light .text_color_111.selected, body.theme_light .selected .text_color_111 { color: #000; }' +
  1276. 'body.theme_dark a.text_color_111:hover, body.theme_dark .text_color_111.selected, body.theme_dark .selected .text_color_111 { color: #FFF; }' +
  1277. 'body.theme_light .text_color_111, body.editor_theme_light #content_text .text_color_111 { color: #111; }' +
  1278. 'body.theme_dark .text_color_111, body.editor_theme_dark #content_text .text_color_111 { color: #EEE; }' +
  1279. 'body.theme_light .text_color_333 { color: #333; }' +
  1280. 'body.theme_dark .text_color_333 { color: #CCC; }' +
  1281. 'body.theme_light .ignored, body.theme_light .ignored a { color: #555 !important; }' +
  1282. 'body.theme_dark .ignored, body.theme_dark .ignored a { color: #AAA !important; }'
  1283. ;
  1284. var $conditional_styles =
  1285. // PSEUDO-ELEMENTS
  1286. '#content_pane[data-content="has_image"] #title span::after { content: attr(data-after); }' +
  1287. '#content_pane[data-content="has_font"] #title::before { content: "Font:" }' +
  1288. '#content_pane[data-content="has_font_file"] #title::before { content: "Glyphs from font:" }' +
  1289. 'body.has_directory_source #title::before { content: "Source of: " }' +
  1290. '#content_pane[data-content="has_grid"] #title::before { content: "Fonts and Images from:"; }' +
  1291. '#content_pane[data-content="has_grid"].has_font_grid #title::before { content: "Fonts from:"; }' +
  1292. '#content_pane[data-content="has_grid"].has_image_grid #title::before { content: "Images from:"; }' +
  1293. '#content_pane[data-content="has_grid"] #title::after { content: "'+ current_dir +'"; }' +
  1294. '#content_pane[data-content="has_ignored"] #title::before { content: "Ignored content:"; }' +
  1295. '#content_pane[data-content="has_dir"] #title::before { content: "Index of:"; }' +
  1296. '#content_pane[data-loaded="unloaded"] #content_container { background-image:'+ SVG_UI_Icon('spinner') +'; background-repeat:no-repeat; background-position:center; background-size:32px; }' +
  1297. 'body #sidebar_title div:before { content: "INDEX OF"; }' +
  1298. 'body.has_playlist #sidebar_title div:before { content: "PLAYLIST"; }' +
  1299. 'body.has_filelist #sidebar_title div:before { content: "FILELIST"; }' +
  1300. '#content_pane.has_audio #content_audio_title span::before, body #content_pane[data-content="has_video"] #title::before' +
  1301. '{ content: "Playing: "; }' +
  1302. '#reload_btn.reset::before, #content_pane.has_zoom_image #reload_btn::before, #content_pane.has_scaled_image #reload_btn::before { content: "Reset"; }' + // reload button
  1303. '#content_pane[data-content="has_text_editor"] #close_btn::before { content: "Hide"; }' + // close button
  1304. '#content_pane[data-content="has_text_editor"] #title::after { content: "Text Editor"; }' +
  1305. '#content_pane[data-content="has_text_editor"].edited #title::after { content: "Text Editor (edited)"; }' +
  1306. 'body.edited #text_editor_row a:after, body.iframe_edited:not(.has_text_editor) #content_pane.has_iframe #title::after' +
  1307. '{ content: " (edited)"; }' +
  1308. 'body.theme_light #theme span::before { content: "Light "; }' +
  1309. 'body.theme_dark #theme span::before { content: "Dark "; }' +
  1310. 'body.show_details #show::before { content: "Hide "; }' +
  1311. '#disable::after { content: "Enabled"; }' +
  1312. 'body.disable_text_editing #disable::after { content: "Disabled"; }' +
  1313. '#disable::after { content: "Enabled"; }' +
  1314. // DISPLAY
  1315. 'body.has_hidden_sidebar #handle, body.has_stats #stats_summary, body[class$="list"] #stats_summary, body.has_stats #footer_links, body[class$="list"] #footer_links, body[class$="list"] #sidebar_buttons label, body.is_error #sidebar_header_body > div:not(:first-of-type), body.is_error #tfoot, body.is_non_local #show_invisibles_container, tr.invisible, tr.ignored, body.hide_ignored_items #tbody tr.ignored, body.hide_ignored_items #tbody tr.ignored, body.hide_ignored_items:not(.show_invisibles) #tbody tr.ignored.invisible, body.hide_ignored_items:not(.show_invisibles) #tbody tr.ignored.invisible, body:not(.show_invisibles) #tbody tr.invisible' +
  1316. '{ display:none; }' +
  1317. 'body.has_media #play_toggle, body[class$="list"] #play_toggle, .media .tbody_row_cell_name_a_span input, body.theme_dark #theme_dark, body.theme_light #theme_light, body.show_details .tbody_row_cell_details, #content_pane[class^="has_"] #close_btn, #content_pane[data-content="has_text_editor"] #close_btn, body[class$="list"] #close_btn' +
  1318. '{ display:unset; }' +
  1319. '#font_info:hover #font_info_body { display:table-row-group; }' +
  1320. 'body[class$="list"] #stats_summary_playlist_files, body.has_help #help_container' +
  1321. '{ display:table-row; }' +
  1322. 'body.has_images #grid_btn, body.has_fonts #grid_btn' +
  1323. '{ display:table-cell; }' +
  1324. 'body.has_menu #menu, body.has_menu_parents #parents_links, #sidebar_menus .has_submenu:hover .submenu, #menu .has_submenu.hovered .submenu, #footer_links:hover ul, body.has_images.has_fonts #grid_btn:hover ul.menu, #content_pane[data-content="has_font_file"] #font_viewer, #content_pane[data-content="has_glyph"] #font_viewer, #tfoot tr, body.has_warning #overlay_container' +
  1325. '{ display:block; }' +
  1326. 'body.show_details #dir_list td.details, body.show_numbers .tbody_row_cell_name_a::before, #content_pane[data-content="has_grid"] .split_btn, #content_pane[data-content="has_image"] .split_btn, #content_pane[data-content="has_font"] .split_btn, #content_pane[data-content="has_font_file"] #title_buttons_left .split_btn, #content_pane[data-content="has_glyph"] #title_buttons_right .split_btn' +
  1327. '{ display:inline-block; }' +
  1328. '#tbody tr:not(.ignored):not(.invisible), body.show_invisibles #tbody tr.invisible, body.has_stats #tbody tr.invisible, body.has_stats #tbody tr.ignored, body.has_stats #tbody tr.ignored.hovered, body.hide_ignored_items.has_stats #tbody tr.ignored, body:not(.hide_ignored_items) #tbody tr.ignored:not(.invisible)' +
  1329. '{ display:grid; }' +
  1330. 'body.show_details #sorting_row_2, body.show_details #text_editor_row, #text_editor_row.has_text_editor, #content_pane.has_audio #content_audio_title, #content_pane.has_audio #content_audio, .playlist_entry_container.has_content, #content_pane .content.has_content:not(#content_iframe), #content_pane[data-loaded="loaded"] #content_iframe, #content_pane[data-content="has_text_editor"] #content_text, #content_pane[data-content="has_font"] #font_specimen, #content_pane[data-content="has_image"] #content_container, #content_pane[data-content="has_image"] #content_image_container, #content_pane[data-content="has_video"] #content_container, body.has_stats #stats_summary_detailed_container, body.has_stats #stats_details_container, body.has_stats .summary_detailed, body[class$="list"] #stats_summary_playlist_container' +
  1331. '{ display:flex; }' +
  1332. '#content_pane[data-content="has_text_editor"] .content.has_content, #content_pane[data-content="has_text_editor"] #content_grid, #content_pane[data-content="has_grid"] #content_text, #content_pane[data-content="has_grid"] .content.has_content' +
  1333. '{display:none !important; }'+
  1334. 'body.theme_dark .invert, body.theme_dark #menu li > span::before, body.theme_dark #sorting span::before, body.theme_dark #sorting span::after' +
  1335. '{ filter:invert(1); }' +
  1336. '#content_pane[data-content="has_ignored"]::before { opacity:0.3; }' +
  1337. 'body.has_warning #sidebar_wrapper, body.has_warning #content_pane, #close_audio:hover::after, body.has_menu_parents #tbody, body.has_menu #tbody, body.faded:not(.has_stats) #tbody tr:not(.hovered), body.has_stats:not(.faded) #tbody tr:not(.hovered), #parent_dir_menu:not(:hover), #menu_container:not(:hover) nav, #toggle_sidebar, #grid_btn, #dir_list.has_dir:not(:hover), #footer_links, .split_btn span, .disabled, body.focus_content #tbody tr:not(.hovered)' +
  1338. '{ opacity: 0.6; }' +
  1339. '#grid_btn:hover, #prev_next_btns span:hover, #toggle_sidebar:hover, #grid_btn:hover, #footer_links:hover, .split_btn span:hover' +
  1340. '{ opacity: 1.0; }' +
  1341. // HAS HIDDEN SIDEBAR
  1342. 'body.has_hidden_sidebar #toggle_sidebar { left:2px; transform:rotate(180deg); }' +
  1343. 'body.has_hidden_sidebar #sidebar_wrapper { width:0 !important; min-width:0; position:absolute; top:2px; left:-1px; }' +
  1344. 'body.has_hidden_sidebar #sidebar_header { z-index:unset; display:none; }' +
  1345. 'body.has_hidden_sidebar #sidebar { visibility:hidden; }' + // allows hidden sidebar to still be navigated by arrows
  1346. 'body.has_hidden_sidebar #dir_list { min-width:0; }' +
  1347. 'body.has_hidden_sidebar #content_pane { width:100% !important; }' +
  1348. 'body.has_hidden_sidebar #title_buttons_left { padding-left:24px; }' +
  1349. // HAS GRID
  1350. '#content_pane[data-content="has_grid"] #content_grid { display: grid; }' +
  1351. '#content_pane.has_hidden_grid #content_grid { max-height:100%; display:grid; overflow:hidden; }' +
  1352. // HAS ZOOM IMAGE
  1353. '#content_pane.has_scaled_image #content_image_container { display:grid; padding:0; }' +
  1354. '#content_pane.has_zoom_image #content_image { width:unset; max-width:unset; max-height:unset; cursor: zoom-out; }' +
  1355. // FIGURE OUT HOW TO DELETE THESE:
  1356. '#content_pane[data-content="has_image"] #title_buttons_left, #content_pane[data-content="has_image"] #title_buttons_right, #content_pane[data-content="has_grid"] #title_buttons_left, #content_pane[data-content="has_grid"] #title_buttons_right, #content_pane[data-content^="has_font"] #title_buttons_left, #content_pane[data-content^="has_font"] #title_buttons_right, #content_pane[data-content^="has_glyph"] #title_buttons_right { min-width: 9.5em; }'
  1357. ;
  1358. var $text_editor_styles =
  1359. 'html, body, #iframe_body { margin:0; padding:0; height:100%; position:relative; font-family:'+ $settings.UI_font +'; font-size: '+ $settings.UI_font_size +'; }' +
  1360. '#iframe_body { display:flex; flex-direction:column; position:relative; overflow:hidden; }' +
  1361. '#iframe_body #content_text { display:flex; height:100%; padding:0; position:absolute; z-index:1; width:100%; overflow:hidden; flex-direction:column; }' + // div
  1362. '.editor_theme_dark .border_right { border-right: solid 1px #111; }' +
  1363. '.editor_theme_light .border_right { border-right: solid 1px #666; }' +
  1364. '.editor_theme_dark .border_bottom { border-bottom: solid 1px #111; }' +
  1365. '.editor_theme_light .border_bottom { border-bottom: solid 1px #666; }' +
  1366. 'ul.has_popout_menu, .editor_theme_light #toolbar ul.has_popout_menu { background-color:#C0C0C0; border:solid 1px #666; }' +
  1367. 'ul.has_popout_menu link { background-color:#D0D0D0; }' +
  1368. 'ul.has_popout_menu li:hover { background-color:#E0E0E0; }' +
  1369. '.editor_theme_dark #toolbar ul.has_popout_menu li { background-color:#C0C0C0; }' +
  1370. $warning_styles +
  1371. // TOOLBAR
  1372. '#toolbar { width:100%; background:#C0C0C0; position:relative; z-index:100; border-bottom: solid 1px #999; border-collapse:collapse; font-family:'+ $settings.UI_font +'; font-size:'+ parseFloat($settings.UI_font_size) * 0.875 + $settings.UI_font_size.replace(/\d*/,'') +'; -webkit-user-select: none; -moz-user-select: none; user-select:none; position:relative; }' + // table
  1373. '#toolbar td { padding:2px 0 0; }' +
  1374. '#toolbar_buttons { margin:0; padding:0; list-style:none; }' + // ul
  1375. '.toolbar_icon { margin:0 4px; padding:4px; width:16px; height:16px; float:left; background-size:14px; background-repeat:no-repeat; background-position:center; cursor:pointer; display:block; opacity:0.6; color:#444; }' + // li
  1376. '#toggle_theme { background-image:'+ SVG_Text_Editing_UI_Icon('toggle_theme') +'; }' +
  1377. '#show_source { background-image:'+ SVG_Text_Editing_UI_Icon('show_markdown') +'; background-size:18px; }' +
  1378. '#show_preview { background-image:'+ SVG_Text_Editing_UI_Icon('show_preview') +'; }' +
  1379. '#show_html { background-image:'+ SVG_Text_Editing_UI_Icon('show_html') +'; background-size:20px; }' +
  1380. '#toggle_split { background-image:'+ SVG_Text_Editing_UI_Icon('toggle_split') +'; }' +
  1381. '#sync_scroll { padding:4px; float:left; opacity:1 !important; }' + // li
  1382. '#sync_scroll input { float:left; }' + //
  1383. '#sync_scroll label { width:8em; display:block; font-size:inherit; line-height:1.5; }' + //
  1384.  
  1385. '#save_btn { margin:0; padding:4px 4px 4px 8px; width:20px; height:16px; float:right; position:relative; z-index:9997; background-repeat:no-repeat; background-position:center right 7px; background-image:'+ SVG_Text_Editing_UI_Icon('save_btn') +'; background-size:16px; cursor:pointer; outline:none; }' +
  1386. '#save_btn ul { display:none; margin:0; padding:0 32px 0 0; position:absolute; top:-4px; right:-2px; list-style:none; box-shadow:0px 4px 6px -3px #333; background-repeat:no-repeat; background-position:right 8px top 7px; background-image:'+ SVG_Text_Editing_UI_Icon('save_btn') +'; background-size:16px; background-color:#C0C0C0; }' +
  1387. '.edited #save_btn, .edited #save_btn ul { background-image:'+ SVG_Text_Editing_UI_Icon('save_btn_edited') +'; }' +
  1388. '#save_btn ul li { margin:0; padding:4px 6px; text-align:right; width:100%; white-space:pre; box-sizing:border-box; }' +
  1389. '#save_btn a { font-weight:normal; }' +
  1390. '#save_btn:hover ul { display:block; }' +
  1391. '#save_btn li:hover { background-color:#DDDDDD !important; }' +
  1392. '.editor_theme_dark #save_btn ul { border-color:#EEE; box-shadow:0px 4px 6px -3px #EEE;}' +
  1393. '.editor_theme_dark #save_btn li { border-color:#EEE; color:#111; background-color:#AEAEAE !important; }' +
  1394. '.editor_theme_dark #save_btn li:hover { background-color:#989898 !important; }' +
  1395. '.editor_theme_dark #save_text { border-color:#EEE; color:#111; }' +
  1396. '#clear_text { margin:0 4px; padding:4px; height:16px; float:right; cursor:pointer; }' + // li
  1397. '#toolbar li:hover, .preview_text:not(.split) #show_preview, body.source_text #show_source, .split_view #toggle_split, .edited #save_btn'+
  1398. '{ opacity:1; }' +
  1399. // TEXT CONTENT CONTAINERS
  1400. '#text_container { display:flex; flex-grow:1; overflow:hidden; }' + // li
  1401. '#text_source { margin:0; padding:1em; display:block; border:0; line-height:1.2; overflow-y:scroll; resize:none; font-family:monospace; box-sizing:border-box; z-index:1; font-size:'+ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') +'; }' + // textarea
  1402. '#text_preview, #html_preview { margin:0; padding:1em; overflow-y:scroll; box-sizing:border-box; z-index:1; }' + // div
  1403. '#html_preview { display:none; white-space:pre-wrap; word-break:break-word; font-family:monospace; }' +
  1404. 'body.preview_html #html_preview { display:block; }' +
  1405. // RESIZE HANDLE
  1406. '#text_editing_handle { display:none; width:7px; position:absolute; top:0; bottom:0; left:calc(50% - 4px); cursor:col-resize; z-index:3; }' + //
  1407. '.split_view #text_editing_handle { display:block; }' +
  1408. // TEXT SOURCE
  1409. '#text_source, #text_preview, #html_preview { height:100%; }' +
  1410. '#text_source:focus, #text_preview:focus, #html_preview:focus { outline:none; }' +
  1411. '#text_source:focus { box-shadow: inset 0 0 4px #666; }' +
  1412. '.editor_theme_dark #text_source:focus { box-shadow: inset 0 0 6px #000; }' +
  1413. // TEXT PREVIEW
  1414. '#text_preview, #html_preview { padding: 14px; box-sizing:border-box; z-index:1; overflow-y:scroll; font-size:'+ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') +'; }' +
  1415. // dark theme
  1416. '.editor_theme_dark #toolbar { background: #404040; color:#EEEEEE; border-bottom: solid 1px #111; }' +
  1417. '.editor_theme_dark.split_view #text_preview, .editor_theme_dark.split_view #html_preview { border-left: solid 1px #111; }' +
  1418. '.editor_theme_dark #toolbar .has_background:not(#save_btn), .editor_theme_dark:not(.edited) #save_btn, .editor_theme_dark.edited #save_btn li' +
  1419. '{ filter: invert(1); }' +
  1420. 'body.theme_light:not(.editor_theme_dark) .background_color_DD_33, body.editor_theme_light .background_color_DD_33 { background-color: #DDDDDD; }' +
  1421. 'body.theme_dark:not(.editor_theme_light) .background_color_DD_33, body.editor_theme_dark .background_color_DD_33 { background-color: #333333; }' +
  1422. 'body.theme_light .background_color_EE_22, body.editor_theme_light .background_color_DD_33:focus, body.theme_light #content_container:hover, body.theme_light #content_grid::after { background-color: #EEEEEE; }' +
  1423. 'body.theme_dark .background_color_EE_22, body.editor_theme_dark .background_color_DD_33:focus, body.theme_dark #content_container:hover, body.theme_dark #content_grid::after { background-color: #222222; }' +
  1424. // SPLIT VIEWS
  1425. '.preview_text:not(.split_view) #text_source, .preview_html:not(.split_view):not(.source_text) #text_source, .source_text:not(.split_view) #text_preview, .source_text:not(.split_view) #html_preview, body.preview_html #text_preview, #html_preview, li#save_btn div, .comment'+
  1426. '{ display:none; }' +
  1427. '.preview_text:not(.split_view) #text_preview, .source_text:not(.split_view) #text_source, .source_text:not(.split_view) #html_preview, .preview_html.preview_text:not(.split_view) #html_preview'+
  1428. '{ width:100% !important; }' +
  1429. '.split_view #text_preview, .split_view #html_preview { width:50%; border-left:solid 1px #999; }' +
  1430. '.split_view #text_source { width:50%; }' +
  1431. // custom previewed text styles
  1432. '#text_preview pre { border:solid 1px #CCC; border-radius:3px; white-space:pre-wrap; word-break:break-word; font-size:'+ parseFloat($settings.UI_font_size) + $settings.UI_font_size.replace(/\d*/,'') +'; }' +
  1433. '#text_preview th, #text_preview td { vertical-align:top; }' +
  1434. '#text_preview blockquote { margin-top:1em; margin-bottom:1em; color: #555; }' +
  1435. '#text_preview blockquote + blockquote { margin-top:0; }' +
  1436. '.markdown-body input[type="checkbox"] { margin-top:0.375em; margin-right:6px; float:left; }' +
  1437. 'h1 .uplink,h2 .uplink,h3 .uplink,h4 .uplink,h5 .uplink,h6 .uplink'+
  1438. '{ display:inline-block; font-size:0.875em; transition: opacity 0.25s; opacity:0; cursor:pointer; margin:0; padding:0; }' +
  1439. 'h1:hover .uplink,h2:hover .uplink,h3:hover .uplink,h4:hover .uplink,h5:hover .uplink,h6:hover .uplink'+
  1440. '{ transition: opacity 0.25s; opacity:0.5; }' +
  1441. '#text_preview table { font-size:inherit; }' +
  1442. '#text_preview table th { background-color:#EEE; }' +
  1443. '.markdown-body::before, .markdown-body::after { display:none !important; background:transparent; }' +
  1444. // disabled text editing
  1445. '#iframe_body.text_editing_disabled #toolbar, #iframe_body.text_editing_disabled #preview_text, #iframe_body.text_editing_disabled #text_editing_handle'+
  1446. '{ display:none; }' +
  1447. '#iframe_body.text_editing_disabled #text_source.disabled { top:0; background:white; }' +
  1448. '#iframe_body.editor_theme_light .background_color_D0_50 { background-color: #D0D0D0; }' +
  1449. '#iframe_body.editor_theme_dark .background_color_D0_50 { background-color: #505050; }' +
  1450. '#iframe_body.editor_theme_light .background_color_E0_60 { background-color: #E0E0E0; }' +
  1451. '#iframe_body.editor_theme_dark .background_color_E0_60 { background-color: #606060; }' +
  1452. '#iframe_body.editor_theme_light .text_color_111, #iframe_body.editor_theme_light #content_text .text_color_111 { color: #111; }' +
  1453. '#iframe_body.editor_theme_dark .text_color_111, #iframe_body.editor_theme_dark #content_text .text_color_111 { color: #EEE; }' +
  1454. '#iframe_body.has_warning::after { content:""; position:absolute; top:0; right:0; bottom:0; left:0; background:rgba(0,0,0,0.33); z-index:9998; }'
  1455. ;
  1456. var $iframe_dir_styles =
  1457. ':root, html, body { margin:0; padding:0; width:100%; max-width:100%; height:100%; border:0; border-radius:0; font-family:'+ $settings.UI_font +'; font-size:'+ $settings.UI_font_size +'; hyphens:auto; overflow:hidden; }' +
  1458. '#iframe_body { width:100%; display:flex; flex-direction:column; font-size:'+ (parseFloat($settings.UI_font_size) * 0.875) + $settings.UI_font_size.replace(/\d*/,'') +'; }' + //
  1459. '#iframe_body.theme_dark { background-color:#333; }' +
  1460. 'a, a:hover { text-decoration: none !important; }' +
  1461. // iframe header
  1462. '#thead { flex-direction:row; font-size: 0.875rem; user-select:none; -webkit-user-select:none; }' +
  1463. '#change_dirs { display:flex; flex-direction:row; justify-content:space-between; white-space:pre; }' +
  1464. '#change_dirs a { padding-right:0 !important; padding-left:0 !important;}' +
  1465. '#change_dirs span:hover { font-weight:bold; }' +
  1466. '#parent { padding:5px 3px 5px 0; text-align:left; flex-grow:1; }' +
  1467. '#open_in_sidebar { padding:5px 0 5px 3px; text-align:right; flex-grow:1; }' +
  1468. '#parent a::before, #open_in_sidebar a::after { content:""; width:16px; height:10px; display:inline-block; }' +
  1469. '#open_in_sidebar a::after { background-image:'+ SVG_UI_Icon('chevron_left') +'; background-repeat: no-repeat; background-size:6px; background-position: center; }' +
  1470. '.theme_dark #parent a::before, .theme_dark #open_in_sidebar a::after { filter:invert(1); }' +
  1471. // sorting
  1472. '#sorting_row_1 div, #sorting_row_2 div { flex-grow:1; padding:0; }' +
  1473. '#sorting_row_1 { display:flex; cursor:pointer; justify-content:space-between; flex-direction:row; }' +
  1474. '#sorting_row_1 div { width:50%; }' +
  1475. '.sorting span { display:inline-block; padding:3px 0; }' +
  1476. '.sorting:hover span { font-weight:bold; }' + //
  1477. '.sorting span::before, .sorting span::after { content:""; width:16px; height:8px; display:inline-block; position:relative; color:#CCC; background-repeat:no-repeat; background-position:center; background-size:10px; }' +
  1478. '.sorting.down span::after { transform:rotate(180deg) }' +
  1479. '.theme_dark .sorting span::before, .theme_dark .sorting span::after { filter:invert(1); }' +
  1480. '#sort_by_name, #sort_by_ext { text-align:left; }' +
  1481. '#sort_by_size, #sort_by_date, #sort_by_default, #sort_by_kind { text-align:right; }' +
  1482. // iframe dir list
  1483. '#iframe_dir_list_wrapper { width:100%; height:100%; display:block; position:relative; overflow:scroll; }' + //
  1484. '#dir_list { width:100%; height:100%; line-height:1.5; position:relative; overflow:hidden; border-collapse:collapse; }' + //
  1485. '#tbody { font-size:0.875rem; counter-reset:row; height:100%; overflow:hidden; }' + //
  1486. '#tbody tr, #sorting_row_2 { display: grid; grid-gap:0; grid-template-columns: minmax(24em,100%) minmax(5.5em,8em) minmax(6.5em,14em) minmax(5.5em,5.5em); }' +
  1487. '.tbody_row_cell_details { white-space:pre; font-variant-numeric:tabular-nums; padding:3px 0; }' +
  1488. '.tbody_row_cell_name_a { padding:3px 0; }' +
  1489. '.tbody_row_cell_name_a::before { display:none; counter-increment:row; content:counter(row); width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; margin-top:2px; padding:0; float:left; text-align:right; font-variant-numeric:tabular-nums; }' +
  1490. '.show_numbers #tbody td.name a::before { display:inline-block; }' +
  1491. '.tbody_row_cell_name_a_span { display:flex; padding:2px 0; word-break:break-word; }' +
  1492. '.tbody_row_cell_name_a_span input { margin:2px 6px 0 0; }' +
  1493. '.tbody_row_cell_details.name { grid-column: 1; text-align:left; }' +
  1494. '.tbody_row_cell_details.size { grid-column: 2; padding-right:16px; padding-left:0; text-align:right; }' +
  1495. '.tbody_row_cell_details.date { grid-column: 3; padding-right:16px; padding-left:0; white-space:normal; overflow:hidden; max-height:1em; overflow-wrap:break-word; text-align:right; }' +
  1496. '.tbody_row_cell_details.date span { white-space:pre; display:inline-block; }' +
  1497. '.tbody_row_cell_details.kind { grid-column: 4; text-align:right; padding-right:16px; }' +
  1498. '.tbody_row_cell_details.ext { display:none; }' +
  1499. 'tr:hover .tbody_row_cell_name_a, tr.selected .tbody_row_cell_name_a, tr.playing .tbody_row_cell_name_a { font-weight:bold; }' +
  1500. 'tr.ignored, tr.ignored a { cursor:not-allowed; }' +
  1501. 'tr.invisible, tr.ignored, body.hide_ignored_items #tbody tr.ignored, body.hide_ignored_items #tbody tr.ignored, body.hide_ignored_items:not(.show_invisibles) #tbody tr.ignored.invisible, body.hide_ignored_items:not(.show_invisibles) #tbody tr.ignored.invisible, body:not(.show_invisibles) #tbody tr.invisible { display:none !important; }' + // display
  1502. 'body.show_invisibles tr.invisible, #tbody tr:not(.ignored):not(.invisible), body.show_invisibles #tbody tr.invisible, body.has_stats #tbody tr.invisible, body.has_stats #tbody tr.ignored, body.has_stats #tbody tr.ignored.hovered, body.hide_ignored_items.has_stats #tbody tr.ignored, body:not(.hide_ignored_items) #tbody tr.ignored:not(.invisible)' +
  1503. '{ display:grid !important; }' +
  1504. '#iframe_body.is_blurred #dir_list { opacity:0.75; }' +
  1505. '#iframe_body tr.is_blurred { background-color: rgba(172,202,235,0.66) !important; }' +
  1506. CSS_UI_Icon_Rules() + // background icons
  1507. '.has_icon_before::before { content:""; display:block; float:left; width:28px; min-width:28px; height:14px; max-height:14px; min-height:14px; margin-top:1px; background-position:center; background-repeat:no-repeat; background-size:14px; }' +
  1508. '#dir_list.sort_by_default #sort_by_default span::before, #dir_list.sort_by_name #sort_by_name span::before, #dir_list.sort_by_size #sort_by_size span::before, #dir_list.sort_by_date #sort_by_date span::before, #dir_list.sort_by_kind #sort_by_kind span::before, #dir_list.sort_by_ext #sort_by_ext span::before'+
  1509. '{ content:""; height:9px; background-image:'+ SVG_UI_Icon('check_mark') +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  1510. '#dir_list.sort_by_default #sort_by_default span::after, #dir_list.sort_by_name #sort_by_name span::after, #dir_list.sort_by_size #sort_by_size span::after, #dir_list.sort_by_date #sort_by_date span::after, #dir_list.sort_by_kind #sort_by_kind span::after, #dir_list.sort_by_ext #sort_by_ext span::after, #parent a::before'+
  1511. '{ background-image:'+ SVG_UI_Icon('chevron_up') +'; background-repeat: no-repeat; background-size:10px; background-position: center; }' +
  1512. // GECKO STYLES:
  1513. 'body.is_gecko #dir_list tr td { min-width:calc(100% - 24px); }' +
  1514. 'body.is_gecko #dir_list tr td.name span { display:-webkit-box; width:calc(100% - 64px); }' +
  1515. 'body.is_gecko .dir::before { content:"" !important; display:none !important; }'
  1516. ;
  1517. // Gecko Styles:
  1518. const $gecko_style_rules =
  1519. 'html, body.is_gecko { border: solid 1px gray !important; }' +
  1520. '.dir::before { content:"" !important; display:none !important; }' +
  1521. 'body.is_gecko button { padding:revert; }' +
  1522. 'body.is_gecko #grid_btn .menu { top:-7px; left:-120px; }' +
  1523. 'body.is_gecko thead { font-size:100%; }' +
  1524. 'body.is_gecko #dir_list .dir::before { position:absolute; }' +
  1525. 'body.is_gecko #dir_list tr td.name span { display:-webkit-box; width:calc(100% - 56px); }' +
  1526. 'body.is_gecko.use_default_icons:not(.is_converted_list) #dir_list .file .name .icon { padding-left:4px; background:none; }' +
  1527. 'body.is_gecko.use_default_icons #dir_list .file .name .icon img { margin-right:6px; height:14px; }' +
  1528. 'body.is_gecko #tbody > tr > td:not(:first-of-type){ float:left }' +
  1529. 'body.is_gecko #content_audio_title span { padding-top: 6px;, padding-bottom: 0; }' +
  1530. 'body.is_gecko #audio,body.is_gecko #audio_container { background-color: transparent; }' +
  1531. 'body.is_gecko.theme_dark #prev_track, body.is_gecko.theme_dark #next_track, body.is_gecko.theme_dark #close_audio { filter: invert(1); }' +
  1532. 'body.is_gecko #content_pane.has_zoom_image #content_image_container { display:block !important;}'
  1533. ;
  1534. const $safari_style_rules =
  1535. 'body.is_safari button { background-color: #FFF; }' +
  1536. 'body.is_safari.theme_dark #prev_track, body.is_safari.theme_dark #next_track, body.is_safari.theme_dark #close_audio { filter: invert(1); }'
  1537. ;
  1538. // ADD STYLES
  1539. const $font_styles = document.createElement('style');
  1540. const $font_grid_styles = document.createElement('style');
  1541. function addStyles(el) {
  1542. el.find('style, link[rel="stylesheet"], link[href$="css"]').remove(); // remove any existing stylesheets
  1543. el.append('<style id="conditional_styles">'+ $conditional_styles +'</style><style id="color_and_background_styles">'+ $color_and_background_styles +'</style><style id="main_styles">'+ $main_styles +'</style>');
  1544. el.append($font_styles); // empty until font is previewed
  1545. el.append($font_grid_styles); // empty until font grid is made
  1546. if ( getBrowser() === 'is_gecko' ) { el.append('<style id="gecko_style_rules">'+ $gecko_style_rules +'</style>'); }
  1547. if ( getBrowser() === 'is_safari' ) { el.append('<style id="safari_style_rules">'+ $safari_style_rules +'</style>'); }
  1548. }
  1549. // ***** END STYLES ***** //
  1550.  
  1551. // ***** INDEX PREP ***** //
  1552. // Try to determine index type from parent directory link container, with fallbacks for indexes that don't have parent directories, or for parent directory links that aren't siblings or ancestors of the index itself.
  1553. function getIndexType() {
  1554. let parent_link_parent, type; // Possible elements: pre, li, td, th, div -- used to determine index type
  1555. // Try to find parent directory link:
  1556. let parent_link = $('a:contains("Parent Directory"), a:contains("parent directory"), a[href="../"], a[href="/"], a[href^="?"], img[alt*="PARENTDIR"]');
  1557. if ( parent_link.length === 0 ) {
  1558. parent_link_parent = document.querySelectorAll('body > ul li, body > pre, body > table:last-of-type tr td');
  1559. } else {
  1560. parent_link_parent = parent_link.parent(); // use original found parent_link
  1561. }
  1562. // If no parent_link_parent found, type = error; else return parent node name
  1563. let node_name = ( parent_link_parent[0] !== undefined ? parent_link_parent[0].nodeName.toLowerCase() : '');
  1564. switch(true) {
  1565. case parent_link_parent.length === 0: node_name = 'error'; break;
  1566. case $protocol.startsWith('file'): if ( getBrowser() === 'is_gecko' ) { node_name = 'gecko'; } break;
  1567. }
  1568. let types = {'gecko':'gecko','li':'list','pre':'pre','th':'table','td':'table','div':'default','error':'error','permission_denied':'permission_denied'};
  1569. type = types[node_name];
  1570. return type;
  1571. }
  1572. // Return Index items, Index type, remove parent directory link, and add body class.
  1573. function getIndexItems() {
  1574. let type = getIndexType(), items;
  1575. switch(type) {
  1576. case 'gecko': items = $('body').find('> table > tbody'); break;
  1577. case 'list': items = $('body').find('> ul'); break;
  1578. case 'pre': items = $('body').find('> pre').html(); break;
  1579. case 'table':
  1580. case 'td':
  1581. if ( $('table > tbody').length === 1 ) {
  1582. items = $('body').find('table > tbody');
  1583. } else {
  1584. items = $('body').find('table'); // tables without tbody
  1585. }
  1586. items.find('tr th, tr td:contains(Parent Directory), a[href="../"], hr').parent('tr').remove();
  1587. break;
  1588. case 'default': items = $('body').find('> table').find('> tbody'); break;
  1589. case 'error': items = $('body').html(); break;
  1590. }
  1591. return [items,type];
  1592. }
  1593. // Index Prep: convert rows and return array of rows, with link, size, date-modified
  1594. function convertIndexItems(type,items) {
  1595. let converted = [];
  1596. switch(type) {
  1597. case 'gecko': converted = convertGeckoType(items); break;
  1598. case 'list': converted = convertListType(items); break;
  1599. case 'pre': converted = convertPreType(items); break;
  1600. case 'table': case 'default': converted = convertTableType(type,items); break;
  1601. case 'error': converted = ''; break;
  1602. }
  1603. return converted;
  1604. }
  1605. // Index Prep: convert list type function
  1606. function convertGeckoType(items) {
  1607. let prepped_index = [];
  1608. const rows = Array.from(items.find('> tr'));
  1609. for ( let row of rows ) {
  1610. let prepped_row = [], cellContents = '', cells = Array.from( $(row).find('> td') ), link = ($(cells).find('a').attr('href'));
  1611. for ( let cell of cells ) {
  1612. cellContents = cell.innerText;
  1613. cellContents = ( cellContents !== undefined ? cellContents.trim() : '');
  1614. prepped_row.push(cellContents);
  1615. }
  1616. prepped_row[1] = prepped_row[1].replace(/\s*KB/,'000'); // convert reported size in KB to total bytes
  1617. prepped_row[2] = prepped_row[2] + ' '+ prepped_row[3];
  1618. prepped_row = prepped_row.slice(1,-1);
  1619. if ( link.length > 0 && link !== '/' && link !== '../' ) { prepped_row.unshift(link); }
  1620. if ( prepped_row.length > 0 ) { prepped_index.push(prepped_row); }
  1621. }
  1622. return prepped_index;
  1623. }
  1624. // Index Prep: convert list type function
  1625. function convertListType(items) {
  1626. let prepped_index = [];
  1627. const rows = items[0].children;
  1628. for ( let i = rows.length; i--; ) {
  1629. let row = rows[i];
  1630. if ( row.innerHTML.indexOf('Parent Directory') === -1 ) {
  1631. let prepped_row = [];
  1632. let link = $(row).find('a').attr('href');
  1633. row = row.innerHTML.replace(/<a .+?<\/a>\s*/,'');
  1634. let cells = row.split(' ');
  1635. for ( let cell of cells ) {
  1636. if ( cell.trim().length > 0 ) { prepped_row.push(cell); }
  1637. }
  1638. if ( link.length > 0 && link !== '/' && link !== '../' ) { prepped_row.unshift(link); }
  1639. if ( prepped_row.length > 0 ) { prepped_index.push(prepped_row); }
  1640. }
  1641. }
  1642. return prepped_index;
  1643. }
  1644. // Index Prep: convert pre type function
  1645. function convertPreType(items) {
  1646. let prepped_index = [];
  1647. const spaces = /\s{2,}/;
  1648. items = items.replace(/\[\s*?\]/g,'[]') // remove empty 'alt=[ ]'
  1649. .replace(/\s*<(hr|img[^>])>\s*/g,'') // remove hr and images
  1650. .replace(/\s*<a href="[^>]+?>\s*Parent Directory\s*<\/a>\s*/g,'') // remove parent links
  1651. .replace(/\s*<a href="\?[^>]+?>[^<]+?<\/a>\s*/g,'') // remove sorting links (href beginning with "?")
  1652. .replace(/(<a [^>]+?>)[^<]+?<\/a>/g,' $1</a> ') // remove link display text and add two spaces after
  1653. .replace(/(\%20){2,}/g,'\%20'); // remove extra spaces in hrefs
  1654. const rows = items.split('\n'); // create array of rows from items
  1655. for ( let i = rows.length; i--; ) {
  1656. let row = rows[i];
  1657. let prepped_row = [];
  1658. let cells = row.split(spaces); // assumes pre-type only uses two or more spaces between "columns"
  1659. let link;
  1660. for ( let j = cells.length; j--; ) {
  1661. let cell = cells[j];
  1662. if ( cell.trim().length > 0 ) {
  1663. if ( !cell.startsWith('<a ') ) { prepped_row.push(cell); } else { link = cell.split('"')[1]; } // extract link
  1664. }
  1665. }
  1666. if ( link !== undefined ) { prepped_row.unshift(link); } // add link to front of prepped_row
  1667. if ( prepped_row.length > 1 ) { prepped_index.push(prepped_row); } // add prepped row to index
  1668. }
  1669. return prepped_index;
  1670. }
  1671. // Index Prep: convert table type function
  1672. function convertTableType(type,items) { // for local chrome indexes and server-generated table-type indexes
  1673. let prepped_index = [];
  1674. const rows = items[0].children;
  1675. for ( let i = rows.length; i--; ) {
  1676. let row = rows[i], prepped_row = [], link, cell, cells = row.cells;
  1677. for ( let j = cells.length; j--; ) {
  1678. switch(true) {
  1679. case cells[j].children[0] !== undefined && cells[j].children[0].nodeName === 'A':
  1680. link = cells[j].children[0].attributes.href.value;
  1681. break;
  1682. case cells[j].innerHTML !== '&nbsp;' && cells[j].innerHTML.indexOf('<img') === -1:
  1683. cell = cells[j].innerHTML.trim().replace(/^-$/m,'—');
  1684. if ( cell.length > 0 ) { prepped_row.push( cell ); }
  1685. break;
  1686. }
  1687. }
  1688. if ( link !== undefined ) { prepped_row.unshift(link); }
  1689. if ( prepped_row.length > 1 ) { prepped_index.push(prepped_row); } // prepped_row.length > 2 in order to omit parent directory row
  1690. }
  1691. return prepped_index;
  1692. }
  1693. // Index Prep: Build new Index from prepped rows
  1694. function buildNewIndex(id,prepped_index,sort,type) {
  1695. let new_index_items = [], body_classes = new Set(), row_link_prefix = '';
  1696. if (type === 'gecko' ) { row_link_prefix = document.baseURI; } // because FF only uses the
  1697. let stats = { total:0, dir:0, file:0, hidden:0, invisible:0, ignored:0, dir_ignore:0, dir_invisible:0, file_invisible:0, file_ignore:0 };
  1698. let selected = ( getQuery('selected').length > 0 ? Number(getQuery('selected')) : null);
  1699. for ( let i = prepped_index.length; i--; ) {
  1700. let row = prepped_index[i];
  1701. // Get row attrs and text
  1702. const row_link = row[0] !== undefined ? row[0].replace(/&amp;/g,'&') : '';// .replace(//,''); // decode some reserved characters
  1703. const row_name = getItemName(row_link).replace(/^\s/m,'\&nbsp;').replace(/^\//m,'').replace(/([-_——])/g,'$1<wbr>'); // display name, with word breaks added after unbreakable chars
  1704. const row_sort_name = getItemName( escapeStr( row_link.toLocaleLowerCase() ) );
  1705. const sizes_and_dates = getItemSizeAndDate(row);
  1706. const row_size = sizes_and_dates[0];
  1707. const row_sort_size = sizes_and_dates[1];
  1708. const row_date = sizes_and_dates[2];
  1709. const row_sort_date = sizes_and_dates[3];
  1710. const row_ext = getItemExt(row_sort_name);
  1711. const row_sort_kind = getItemKind(row_ext);
  1712. const row_kind = row_sort_kind.slice(0,1).toUpperCase() + row_sort_kind.slice(1);
  1713. let row_classes = getItemClasses(row_sort_name,row_sort_kind,row_ext);
  1714. if ( window.self === window.top && selected !== undefined && ( prepped_index.length - i ) === selected ) { row_classes += ' selected'; } // autoSelect file
  1715. // Assemble row elements
  1716. const checkbox = ( /media/.test(row_classes) ? '<input type="checkbox" tabindex="-1" checked />' : '');
  1717. const name_span = '<span class="tbody_row_cell_name_a_span has_icon_before">'+ checkbox + row_name +'</span>';
  1718. const cell_link = '<a href="'+ row_link_prefix + row_link +'" class="icon tbody_row_cell_name_a text_color_111">'+ name_span +'</a>';
  1719. const cell_name = '<td class="tbody_row_cell tbody_row_cell_name name" data-name="'+ row_sort_name.split('/')[0] +'">'+ cell_link +'</td>';
  1720. const cell_size = '<td class="tbody_row_cell_details size details" data-size="'+ row_sort_size +'">'+ row_size +'</td>';
  1721. const cell_date = '<td class="tbody_row_cell_details date details" data-date="'+ row_sort_date +'">'+ row_date +'</td>';
  1722. const cell_kind = '<td class="tbody_row_cell_details kind details" data-kind="'+ row_sort_kind +'">'+ row_kind +'</td>';
  1723. const cell_ext = '<td class="tbody_row_cell_details ext details" data-ext="'+ row_ext +'"></td>';
  1724. // Assemble row
  1725. let new_row = '<tr id="rowid-'+ (prepped_index.length - i) +'" class="'+ row_classes+'" data-kind="'+ row_sort_kind +'" data-ext="'+ row_ext +'">'+ cell_name + cell_size + cell_date + cell_kind + cell_ext +'</tr>';
  1726. new_index_items.push($(new_row)[0]);
  1727. // body classes (set object: unique values)
  1728. switch(row_sort_kind) {
  1729. case 'audio': body_classes.add('has_media').add('has_audio'); break;
  1730. case 'video': body_classes.add('has_media').add('has_video'); break;
  1731. case 'font' : body_classes.add('has_fonts'); break;
  1732. case 'image': body_classes.add('has_images'); break;
  1733. }
  1734. switch(type) {
  1735. case 'gecko': body_classes.add('is_converted_gecko'); break;
  1736. case 'list': body_classes.add('is_converted_list'); break;
  1737. case 'pre': body_classes.add('is_converted_pre'); break;
  1738. case 'table':
  1739. case 'td': body_classes.add('is_converted_table'); break;
  1740. case 'default': body_classes.add('is_default'); break;
  1741. case 'error': body_classes.add('is_error'); break;
  1742. }
  1743. // Stats for visible files
  1744. stats.total = ++stats.total; // increment total number of items
  1745. if ( row_sort_kind === 'dir' ) { ++stats.dir; } else { ++stats.file; } // increment total number of dirs or files
  1746. if ( !stats[row_kind] && !/invisible|ignored/.test(row_classes) ) { stats[row_kind] = 1; } else { ++stats[row_kind]; } // increment row_kind or add it to the stats if it is not already there
  1747. // stats for invisible/ignored files
  1748. if ( /invisible|ignored/.test(row_classes) ) { // if it's invisible or ignored --> why is this so complicated?
  1749. ++stats.hidden;
  1750. if ( /invisible/.test(row_classes) ) { // if it's invisible...
  1751. ++stats.invisible; // increment the invisible count
  1752. if ( !stats[row_kind +'_invisible'] ) { stats[row_kind +'_invisible'] = 1; } else { ++stats[row_kind +'_invisible']; } // increment row_kind or add it
  1753. if ( /dir/.test(row_classes) ) { // if it's an invisible directory...
  1754. if ( !stats.dir_invisible ) { stats.dir_invisible = 1; } else { ++stats.dir_invisible; } // increment count or add it to the list
  1755. } else { // or an invisible file
  1756. if ( !stats.file_invisible ) { stats.file_invisible = 1; } else { ++stats.file_invisible; } // increment count or add it to the list
  1757. }
  1758. } else { // else it is ignored
  1759. ++stats.ignored; // increment the ignored count
  1760. if ( !stats[row_kind +'_ignore'] ) { stats[row_kind +'_ignore'] = 1; } else { ++stats[row_kind +'_ignore']; }
  1761. if ( /dir/.test(row_classes) ) { // if it's an ignored directory... (is there such a thing?)
  1762. if ( !stats.dir_ignore ) { stats.dir_ignore = 1; } else { ++stats.dir_ignore; }
  1763. } else { // on an ignored file...
  1764. if ( !stats.file_ignore ) { stats.file_ignore = 1; } else { ++stats.file_ignore; }
  1765. }
  1766. }
  1767. }
  1768. }
  1769. $('body').addClass(Array.from(body_classes).join(' ')).data('stats',stats); // add stats data to body and body classes
  1770. if ( sort === undefined ) { sort = getQuery('sort_by'); } // get sorting pref
  1771. let sort_direction = ( getQuery('sort_direction') === 'up' ? -1 : 1 ); // get sort direction
  1772. let sorted_index_items = sortDirList($(new_index_items), 'sort_by_'+ sort, sort_direction); // make initial sort
  1773. return sorted_index_items;
  1774. }
  1775. // Index Prep: get row name
  1776. function getItemName(link) {
  1777. let name, url;
  1778. try { name = decodeURIComponentSafe(link); }
  1779. catch (error) { name = link.replace(/%20/g,' ').replace(/\s{2,}/,' '); }
  1780. if ( name.split('/').length > 2 ) { // get name only if x = full path
  1781. let arr = name.split('/');
  1782. if ( name.startsWith('/') && name.endsWith('/') ) { // dirs
  1783. name = arr[arr.length - 2] + '/';
  1784. } else {
  1785. name = arr[arr.length - 1]; // files
  1786. }
  1787. }
  1788. return name;
  1789. }
  1790. // Index Prep: get row classes
  1791. function getItemClasses(name,kind,ext) {
  1792. if ( ext === undefined ) { ext = ''; }
  1793. let item_classes = [];
  1794. item_classes.push(kind);
  1795. if ( !/dir|app/.test(ext) ) { item_classes.push('file'); }
  1796. if ( ext === 'app' ) { $settings.apps_as_dirs === false ? item_classes.push('file') : item_classes.push('dir') }
  1797. if ( name.endsWith('symlink') || name.endsWith('alias') || name.endsWith('symbolic link') ) { item_classes.push('alias'); }
  1798. if ( name.startsWith('.') ) { item_classes.push('invisible'); }
  1799. for ( let item_kind_system of $item_kind.system ) { if ( name.endsWith(item_kind_system) ) { item_classes.push('ignored'); } } // ignore various system items
  1800. switch(true) {
  1801. case !JSON.stringify( $item_kind).match( escapeStr(ext) ) : // else classify as "other" if extension is not in $item_kind.
  1802. item_classes.push('other'); //itemType = 'Other'; itemKind = 'other';
  1803. break;
  1804. case kind === '':
  1805. // item_classes.push('code'); //itemType = 'Code'; itemKind = 'code';
  1806. break;
  1807. case kind === 'system':
  1808. item_classes.push('ignored');
  1809. default:
  1810. item_classes.push(ext);
  1811. }
  1812. if ( $row_settings.ignored.includes( ext ) ) { item_classes.push('ignored'); }
  1813. if ( $row_settings.exclude.includes( ext ) ) { item_classes.push('exclude'); }
  1814. if ( kind === 'audio' || kind === 'video' ) { item_classes.push('media'); }
  1815. item_classes = Array.from(new Set(item_classes)); // remove dupe classes
  1816. return item_classes.join(' ');
  1817. }
  1818. // Index Prep: get formatted row size and date
  1819. function getItemSizeAndDate(cells) {
  1820. let sizes_and_dates = [], row_display_size, row_sort_size, row_display_date, row_sort_date, size_units = /[BYTES|B|K|KB|MB|GB|TB|PB|EB|ZB|YB]/;
  1821. if ( cells.length > 1 ) {
  1822. if ( cells[1].search(/[-:\/]/) !== -1 ) { // test for typical date/time separators.
  1823. row_display_date = cells[1]; row_display_size = cells[2];
  1824. } else {
  1825. row_display_date = cells[2]; row_display_size = cells[1];
  1826. }
  1827. }
  1828. // size
  1829. if ( /undefined|—|-|,/.test(row_display_size) || row_display_size === '' ) { // if size is undefined, empty, or punctuation
  1830. row_display_size = '&mdash;'; row_sort_size = '0'; // if no size supplied, use these defaults
  1831. } else {
  1832. row_sort_size = getItemSortSize(row_display_size);
  1833. if ( !row_display_size.toUpperCase().match(size_units) ) { // if provided size is only numeric
  1834. row_display_size = formatBytes(row_display_size,1); // format byte size
  1835. } else {
  1836. row_display_size = row_display_size.replace('K','k').replace(/(\d+)\s*([A-z])/,'$1 $2'); // ensure display size has space between number and units
  1837. }
  1838. }
  1839. // date
  1840. if ( [undefined,'','-'].includes(row_display_date) ) { row_display_date = '&mdash;'; row_sort_date = '0'; } else { row_sort_date = getItemDate(row_display_date); }
  1841. row_display_date = row_display_date.replace(/, (.+)/,'<span>, $1</span>'); // add span for short date
  1842. sizes_and_dates.push( row_display_size, row_sort_size, row_display_date, row_sort_date );
  1843. return sizes_and_dates;
  1844. }
  1845. // Index Prep: get row size for sorting
  1846. function getItemSortSize(val) {
  1847. let sort_size, values = val.replace(/(\d+)\s*([A-z]+)/,'$1 $2').split(' '), size = values[0], unit = values[1];
  1848. 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
  1849. if ( unit !== undefined ) { unit = unit.toUpperCase(); }
  1850. sort_size = size * factor[unit]; // convert byte size to multiplication factor
  1851. return sort_size;
  1852. }
  1853. // convert numeric sizes to display format
  1854. function formatBytes(val, decimals) {
  1855. if (val === 0) return '0 Bytes';
  1856. const k = 1024, dm = (decimals < 0 ? 0 : decimals), sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], i = Math.floor(Math.log(val) / Math.log(k));
  1857. return parseFloat((val / Math.pow(k, i)).toFixed(dm)) +' '+ sizes[i];
  1858. }
  1859. // process date
  1860. function processDate(match,p1,p2,p3) { //date formats: 2017-10-09 13:12 || 2015-07-25T02:02:57.000Z || 12-Mon-2017 21:11
  1861. const mo = 'JanFebMarAprMayJunJulAugSepOctNovDec'.indexOf(p2)/3 + 1; // e.g., convert month into number, or use number
  1862. return p3 +'-'+ mo +'-'+ p1;
  1863. }
  1864. // Index Prep: get row date 2015-07-25T02:22:00.000Z
  1865. function getItemDate(val) {
  1866. let sort_date = val.replace(/^(\d{2})-(\w{3})-(\d{4})/m, processDate) // convert Month to number
  1867. .replace(/\b(\d{1})[-:/]/g,'0$1/') // add leading 0 for single digit numbers
  1868. .replace(/(\d{2})\/(\d{2})\/(\d{2}),/,'$3$1$2') // reorder MM/DD/YY dates
  1869. .replace(/-|:|\s+|\//g,''); // remove spacing characters
  1870. return sort_date;
  1871. }
  1872. // Index Prep: get row kind
  1873. function getItemKind(ext) {
  1874. let kind = '';
  1875. switch(true) {
  1876. case ( /dir|app/.test(ext) ): kind = ext; break; // directories and apps
  1877. case ( /url|url\/|.*webloc|.*webloc\//.test(ext) ): kind = 'link'; break; // links
  1878. default: // categorize everything else by $item_kind
  1879. for ( let types in $item_kind ) { if ( $item_kind[types].includes( ext ) ) { kind = types; return kind; } else { kind = 'other'; } }
  1880. }
  1881. return kind;
  1882. }
  1883. // Index Prep: get row extension
  1884. function getItemExt(item_sort_name) {
  1885. let ext = '';
  1886. switch(true) {
  1887. case item_sort_name.endsWith('app/'): case item_sort_name.endsWith('exe'): ext = 'app'; break; // apps
  1888. case item_sort_name.endsWith('symlink'): ext = 'symlink'; break;
  1889. case item_sort_name.endsWith('/'): ext = 'dir'; break; // directories
  1890. case !/\./.test(item_sort_name): ext = item_sort_name; // if no '.' in link (typical for bin files), ...
  1891. default: // find the last . and get the remaining characters
  1892. ext = item_sort_name.slice(item_sort_name.lastIndexOf('.') + 1);
  1893. if ( /\?/.test(ext) ) { ext = ext.slice(0,ext.indexOf('?')); }
  1894. }
  1895. return ext;
  1896. }
  1897. // END INDEX PREP
  1898. // ***** MAKE NEW INDEX ***** //
  1899. function makeNewIndex(el,sort) {
  1900. const index_items = getIndexItems(el), items = index_items[0], type = index_items[1];
  1901. const converted_index = convertIndexItems( type, items ); // = array of rows: ["link","date","size"]
  1902. switch(type) {
  1903. case 'error': return [converted_index]; break;
  1904. default: let new_index = buildNewIndex( $(el).attr('id'), converted_index, sort, type ); return [new_index];
  1905. }
  1906. }
  1907. // get and set index stats
  1908. function sortObject(obj) { return Object.keys(obj).sort().reduce((r, k) => (r[k] = obj[k], r), {}); }
  1909.  
  1910. function getIndexStats() {
  1911. if ( $('body').data('stats') === undefined ) { return; }
  1912. var stats = $('body').data('stats'), i = 1, padding_style = '';
  1913. stats = sortObject(stats); // sort by kind
  1914. const total_hidden = '('+ stats.hidden +' invisible or ignored)';
  1915. const hidden_dirs = ' ('+ (stats.dir_invisible + stats.dir_ignore) +' invisible or ignored)';
  1916. const hidden_files = ' ('+ (stats.file_invisible + stats.file_ignore) +' invisible or ignored)';
  1917. const dirs_label = ( stats.dir === 1 ? ' Directory' : ' Directories' );
  1918. const files_label = ( stats.file === 1 ? ' File' : ' Files' );
  1919. let details_rows = '';
  1920. var display_name = '', hidden_name = '';
  1921. for ( let stat in stats ) {
  1922. if ( i !== 1 ) { padding_style = 'first'; }
  1923. if ( Number.isNaN(stats[stat]) ) { stats[stat] = 0; }
  1924. display_name = stat.replace(/_(.+)/,' ($1)');
  1925. if ( !/total|dir|file|^hidden|^invisible|^ignored/m.test(stat) ) { // don't include these stats in details rows
  1926. details_rows += (`<div class="${stat.toLowerCase()}"><span class="stats_count ${padding_style}">${stats[stat]} </span><span class="stats_kind ${stat.replace('_',' ').toLowerCase()} ${padding_style}"><a class="icon"><span class="has_icon_before">${display_name}${hidden_name}</span></a></span></div>`); // create list of kinds and counts
  1927. }
  1928. i = 0;
  1929. }
  1930. $('#stats_summary').html(stats.total +' Items: '+ stats.dir + dirs_label +', '+ stats.file + files_label);
  1931. $('#stats_summary_detailed_total').html( '<span class="stats_count ">'+ stats.total +' Items '+ total_hidden +'</span>' );
  1932. $('#stats_summary_detailed_dirs').html( '<span class="stats_count">'+ stats.dir +'</span><span class="stats_kind dir has_icon_before">'+ dirs_label + hidden_dirs +'</span>' );
  1933. $('#stats_summary_detailed_files').html( '<span class="stats_count">'+ stats.file +'</span><span class="stats_kind file has_icon_before">'+ files_label + hidden_files +'</span>' );
  1934. $('#total').html( stats.total +' Items' );
  1935. $('#total_hidden').html( total_hidden );
  1936. $('#dirs_length').html( stats.dir );
  1937. $('#dirs_label_hidden_dirs').html( dirs_label + hidden_dirs );
  1938. $('#files_length').html( stats.file );
  1939. $('#files_label_hidden_files').html( files_label + hidden_files );
  1940. $('#stats_details_container').empty().append( details_rows );
  1941. }
  1942. // ***** END DIR_LIST SETUP ***** //
  1943. const $body = $('body');
  1944.  
  1945. // ***** UI SETUP ***** //
  1946. // UI Setup: build UI and add body classes and other initial settings
  1947. function setupUIprefs() {
  1948. if ( window.location.href.endsWith('?') ) { window.location.href = window.location.href.slice(0,-1) } // fix for situations where the query isn't completely removed
  1949. for ( let key in $settings ) {
  1950. switch(true) {
  1951. case getQuery(key) === 'true': // only add body class for $settings whose value is true
  1952. $body.addClass(key);
  1953. if ( getQuery('toggle_sidebar') === 'true' ) { $body.addClass( 'has_hidden_sidebar' ); }
  1954. if ( getQuery('show_invisibles') === 'true' ) { $('#show_invisibles').attr('checked','checked'); }
  1955. if ( getQuery('default_text_view') === 'true' ) { $body.addClass($settings.default_text_view.toString()); }
  1956. break;
  1957. default: // non-boolean settings and others
  1958. switch(true) {
  1959. case key === 'theme':
  1960. $body.addClass( 'theme_'+ getQuery('theme') );
  1961. break;
  1962. case key === 'sort_by':
  1963. $body.addClass( 'sort_by_'+ getQuery(key) );
  1964. $('#sort_by_'+ getQuery(key) ).addClass('selected');
  1965. if ( getQuery('sort_direction') === 'up' ) { $('#sort_by_'+ getQuery(key) ).addClass('up'); }
  1966. break;
  1967. case key === 'default_text_view':
  1968. if ( getQuery(key) === 'source_text' ) { $body.addClass('source_text'); } else { $body.addClass('preview_text'); }
  1969. break;
  1970. case getQuery('editor_theme') !== '':
  1971. $body.addClass( 'editor_theme_'+ getQuery('editor_theme'));
  1972. break;
  1973. }
  1974. }
  1975. }
  1976. switch( getBrowser() ) {
  1977. case 'is_chrome': $body.addClass('is_chrome'); break;
  1978. case 'is_gecko': $body.addClass('is_gecko'); break;
  1979. case 'is_safari': $body.addClass('is_safari'); break;
  1980. }
  1981. if ( navigator.platform.indexOf('Win') > -1 ) { $body.addClass('is_windows'); }
  1982. // if ( $protocol !== 'file:' ) { $body.addClass('is_non_local'); }
  1983. }
  1984.  
  1985. // Build UI: Append all assembled elements to $body
  1986. function buildUI() {
  1987. switch(true) {
  1988. case window.self === window.top: // if it's not an iframe...
  1989. document.title = 'Index of: '+ current_location;
  1990. $('head').prepend('<meta charset="utf-8"><base href="'+ document.baseURI +'">');// .find('#title').removeAttr('id');
  1991. if ( current_location.startsWith('file') ) { // add favicon for local directories
  1992. $('head').prepend('<link href="data:image/png;base64,' + SVG_UI_File_Icon('favicon') +'" rel="icon" sizes="16x16" />');
  1993. }
  1994. const new_index = makeNewIndex('body'); // make index
  1995. // setup
  1996. switch(true) {
  1997. case $('body').hasClass('is_error'):
  1998. $('#content_pane').addClass('has_iframe');
  1999. $('#content_iframe').attr('src',current_location +'/?is_error=true').addClass('has_content');
  2000. break;
  2001. default:
  2002. $main_content.find('#tbody').append(new_index); // append index to body
  2003. $('body').empty().attr('id','top').attr('lang','en').append($main_content);
  2004. getIndexStats();
  2005. setupUIprefs();
  2006. updateParentLinks();
  2007. addStyles( $('head') );
  2008. initMedia();
  2009. autoLoadFile();
  2010. }
  2011. break;
  2012. case window.self !== window.top && !window.location.pathname.endsWith('.pdf'):
  2013. setUpIframeUI();
  2014. break;
  2015. }
  2016. }
  2017. buildUI();
  2018.  
  2019. // SET UI TO DEFAULT SETTINGS: remove queries;
  2020. function defaultSettings() {
  2021. let query_str = '';
  2022. if ( getQuery('selected') !== undefined ) { query_str += 'selected='+ getQuery('selected'); }
  2023. if ( getQuery('history') !== undefined ) { query_str += 'history='+ getQuery('history'); }
  2024. if ( query_str.length > 0 ) { query_str = '?' + query_str.replace(/\s/g,'+'); }
  2025. window.location.assign(current_location + query_str);
  2026. }
  2027. $('#default_settings').on('click', function(e) {
  2028. e.preventDefault(); e.stopPropagation();
  2029. $('body').removeClass('has_menu');
  2030. if (window.confirm( 'Are you sure you want to remove all your temporary UI settings from the URL query string?' ) ) { defaultSettings(); }
  2031. });
  2032. // SAVE USER SETTINGS
  2033. function saveSettings(file_name, data) { saveFile(data,'application/json',file_name); }
  2034. $('#export_settings').on('click',function(e) {
  2035. e.preventDefault(); e.stopPropagation();
  2036. $('body').removeClass('has_menu');
  2037. const settings_string = ( JSON.stringify($settings,null,'\t'));
  2038. saveSettings('settings.json',settings_string);
  2039. });
  2040. // Show/Close Help
  2041. function showHelp() { $('body#top').addClass('has_help'); }
  2042. $('#show_help').on('click','span', function(e) { e.stopPropagation(); showHelp(); });
  2043. $('#close_help').on('click', function(e) { e.preventDefault(); $('body').removeClass('has_help'); });
  2044. $('#help_container').on('click', function(e) { e.stopPropagation(); });
  2045. // Click Menu Bookmark (with warning)
  2046. function setLocation(link) { window.location = link; }
  2047. $('#parent_dir_nav, #parents_dir_nav + .menu, #menu .bookmark').on('click','a', function(e) {
  2048. e.preventDefault();
  2049. switch(true) {
  2050. case $(this).attr('href').indexOf('file://') > -1 && $protocol !== 'file:':
  2051. $body.addClass('has_warning').find('#warnings_container').addClass('warning_local_bookmark'); $('#warning_btn_ok').focus(); break;
  2052. case $body.hasClass('has_playlist'): case $body.hasClass('has_filelist'): closePlaylist(); break;
  2053. default: showWarning( 'setLocation', $(this).attr('href') );
  2054. }
  2055. });
  2056. // Show Menus
  2057. function showMenus(el) {
  2058. sendMessage('iframe','top_has_menu');
  2059. $('body').removeClass('has_hidden_sidebar');
  2060. let position = $(el).position(), id = $(el).attr('id');
  2061. if ( $('body').hasClass('has_stats') ) { $('#stats').click(); }
  2062. $(el).find('> ul').css({'top':position.top + $(el).innerHeight() + 1 + 'px'});
  2063. switch(id) {
  2064. case 'parents_dir_menu':
  2065. switch(true) {
  2066. case $('body').hasClass('has_menu_parents'): $('body').removeClass('has_menu_parents'); break;
  2067. default: $('body').addClass('has_menu_parents').removeClass('has_menu');
  2068. }
  2069. break;
  2070. case 'menu_container':
  2071. switch(true) {
  2072. case $('body').hasClass('has_menu'): $('body').removeClass('has_menu'); sendMessage('iframe','top_closed_menu'); break;
  2073. default: $('body').addClass('has_menu').removeClass('has_menu_parents'); sendMessage('iframe','top_has_menu');
  2074. }
  2075. }
  2076. }
  2077. $('#parents_dir_menu, #menu_container').on('click',function(e) { e.stopPropagation(); showMenus($(this)); });
  2078. // Click menu
  2079. function clickMenu() {
  2080. $('#menu').find('.selected:not(.hovered)').removeClass('selected').find('a,> span,label').click();
  2081. $('#menu').find('.hovered').removeClass('selected hovered');
  2082. $('body').removeClass('has_menu');
  2083. if ( $('body').hasClass('focus_content') ) { sendMessage('iframe','close_menu'); $('#content_iframe').focus(); }
  2084. if ( $body.hasClass('focus_content') ) { focusContent(); }
  2085. }
  2086. // Close menus
  2087. function closeMenus() {
  2088. switch(true) {
  2089. case !$('body').hasClass('has_menu') && !$('body').hasClass('has_menu_parents') && !$('body').hasClass('has_stats'): break;
  2090. default: $('body').removeClass('has_menu has_menu_parents faded has_stats has_help');
  2091. }
  2092. }
  2093. // Show Stats
  2094. function showStats() { $('body').addClass('has_stats').removeClass('has_menu has_menu_parents'); }
  2095. $('#stats_summary').on('click', function(e) { e.stopPropagation(); showStats(); });
  2096. $(document).on('click', function(e) { e.stopPropagation(); closeMenus(); });
  2097.  
  2098. // Toggle UI Preferences
  2099. function toggleUIpref(pref_ID) {
  2100. let pref_class = pref_ID;
  2101. if ( $body.hasClass('preview_text') && pref_ID ==='preview_text' || $body.hasClass('source_text') && pref_ID ==='source_text' ) { return; }
  2102. switch(pref_ID) {
  2103. case 'sort_by_name': case 'sort_by_default': case 'sort_by_size': case 'sort_by_date': case 'sort_by_kind': case 'sort_by_ext': // sorting
  2104. $('body').removeClass('has_menu sort_by_default sort_by_name sort_by_size sort_by_date sort_by_kind sort_by_ext');
  2105. break;
  2106. case 'theme': // theme
  2107. if ( $('body').hasClass('theme_dark') ) { pref_ID = 'theme_dark'; } else { pref_ID = 'theme_light'; }
  2108. pref_class = 'theme_dark theme_light';
  2109. sendMessage('iframe',pref_ID);
  2110. break;
  2111. case 'source_text':
  2112. case 'preview_text': // text editing default view
  2113. pref_class = 'preview_text source_text';
  2114. pref_ID = 'default_text_view';
  2115. sendMessage('iframe',pref_ID);
  2116. break;
  2117. case 'split_view': case 'alternate_background': case 'hide_ignored_items': case 'show_invisibles': case 'show_numbers':
  2118. sendMessage('iframe',pref_ID);
  2119. break;
  2120. }
  2121. toggleQuery(pref_ID);
  2122. $('body#top').toggleClass(pref_class);
  2123. if ( pref_ID === 'disable_text_editing' ) { // needs to be here and not in switch above
  2124. $('.selected.text, .selected.markdown, .selected.code').find('a').click();
  2125. }
  2126. scrollThis('tbody','selected',false); // true = instant scroll
  2127. $('#stats').html(getIndexStats($('#dir_list').find('#tbody tr')));
  2128. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  2129. }
  2130. // Click Toggle UI Pref elements
  2131. $('.toggle_UI_pref').on('click',function(e) {
  2132. if ( !$(this).is('input') ) { e.preventDefault(); } // allow checkboxes to be checked
  2133. if ( !$(this).hasClass('disabled') ) { toggleUIpref( $(this).attr('id') ); }
  2134. });
  2135.  
  2136. // Toggle Sidebar
  2137. function toggleSidebar() {
  2138. $body.toggleClass('has_hidden_sidebar');
  2139. if ( $body.hasClass('has_hidden_sidebar') ) { setQuery('toggle_sidebar','true'); } else { removeQuery('toggle_sidebar'); }
  2140. if ( $body.hasClass('focus_content') ) { focusContent(); }
  2141. scrollThis('tbody','selected',true); // true = instant scroll
  2142. }
  2143. $('#toggle_sidebar').on('click', function(e) { e.stopPropagation(); toggleSidebar(); });
  2144.  
  2145. // RESIZE Sidebar/Content Pane
  2146. function resizeSidebar(f) {
  2147. f.stopPropagation();
  2148. let $sidebar_wrapper = $('#sidebar_wrapper'), startX = f.pageX, window_width = window.innerWidth, sidebar_width = $sidebar_wrapper.width();
  2149. $('body').addClass('has_overlay'); // prevent interference from the rest of ui
  2150. $(document).on('mousemove',function(e) {
  2151. e.stopPropagation(); e.preventDefault();
  2152. let deltaX = e.pageX - startX;
  2153. if ( e.pageX > 230 && e.pageX < window_width - 200 ) {
  2154. $sidebar_wrapper.css({'width':sidebar_width + deltaX + 'px'});
  2155. $content_pane.css({'width':(window_width - sidebar_width) - deltaX + 'px'});
  2156. }
  2157. scrollThis('tbody','selected',false); // true = instant scroll
  2158. });
  2159. $(document).on('mouseup',function() {
  2160. $('body').removeClass('has_overlay'); // remove the overlay
  2161. $(document).off('mousemove'); // remove eventlistener
  2162. setQuery('width',$sidebar_wrapper.width()); // set the sidebar width query
  2163. if ( $body.hasClass('focus_content') ) { focusContent(); } // refocus content if necessary
  2164. });
  2165. }
  2166. $('#handle').on('mousedown', function(f) { f.stopPropagation(); resizeSidebar(f); });
  2167.  
  2168. // ***** BASIC UI FUNCTIONS ***** //
  2169. // getElementById alias
  2170. function getElById(id) { return $(document.getElementById(id) ); }
  2171.  
  2172. // Scroll Selected Items
  2173. function scrollThis(container_ID, scroll_el_class, bool) {
  2174. let $container = document.getElementById(container_ID);
  2175. if ( $container.height === 0 ) { return; } // don't scroll hidden elements
  2176. let $scroll_el = ( scroll_el_class !== undefined ? $container.getElementsByClassName(scroll_el_class) : null );
  2177. let scroll_behavior = ( ( bool !== undefined || bool === true || $content_pane.attr('data-content') === 'has_grid' ) ? 'instant' : 'smooth' ); // instant allows sidebar & grid to scroll simultaneously
  2178. let scroll_block = ( $('body').hasClass('is_gecko') ? 'start' : 'nearest' );
  2179. if ( $scroll_el !== undefined && $scroll_el.length === 1 ) {
  2180. $scroll_el[0].scrollIntoView({ behavior:scroll_behavior, block:scroll_block, inline:'nearest' });
  2181. }
  2182. }
  2183. // ***** SORTING ***** //
  2184. function sortIndex(els,id,sort_direction) { // id = sort type
  2185. let sorted = [];
  2186. const new_sort = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); // needs to be above makeNewIndex()
  2187. sorted = els.removeClass('sorted border_top').sort((a, b) => {
  2188. let aName, bName;
  2189. if ( $('body').hasClass('has_playlist') || $('body').hasClass('has_filelist') ) {
  2190. aName = $(a).attr('id'); bName = $(b).attr('id');
  2191. } else {
  2192. aName = $(a).find('td.name').data('name'); bName = $(b).find('td.name').data('name');
  2193. }
  2194. let aData = $(a).find('td[data-'+ id +']').data(id);
  2195. let bData = $(b).find('td[data-'+ id +']').data(id);
  2196. if ( sort_direction === 1 ) { // sort by detail data value, then by name (i.e., if data values are the same, sort by name)
  2197. if ( new_sort.compare(aData, bData) === 0 ) { return new_sort.compare(aName, bName); } else { return new_sort.compare(aData, bData); }
  2198. } else { // reverse sort
  2199. if ( new_sort.compare(bData, aData) === 0 ) { return new_sort.compare(bName, aName); } else { return new_sort.compare(bData, aData); }
  2200. }
  2201. });
  2202. sorted = sorted.sort((a, b) => { // add sorted style (rules between different rows)
  2203. if ( id === 'kind' || id === 'ext' && sort_direction === 1 ) {
  2204. if ( $(a).find('td[data-'+ id +']').data(id) !== $(b).find('td[data-'+ id +']').data(id) ) { $(a).addClass('sorted border_top'); }
  2205. }
  2206. if ( id === 'kind' || id === 'ext' && sort_direction === -1 ) {
  2207. if ( $(b).find('td[data-'+ id +']').data(id) !== $(a).find('td[data-'+ id +']').data(id) ) { $(a).addClass('sorted border_top'); }
  2208. }
  2209. });
  2210. return sorted;
  2211. }
  2212. // Sort the Dir List on click
  2213. function sortDirList(rows, id, sort_direction) {
  2214. let $sorted = [], $sort_all = rows, $sort_dirs = $sort_all.filter('.dir:not(.app)'), $sort_files = $sort_all.filter('.file,.app'), sort_id = id.slice(8);// === 'default' ? 'name' : id;
  2215. if ( sort_id === 'default' || ( sort_id !== 'name' && $settings.dirs_on_top === true )) {
  2216. const $sorted_dirs = sortIndex($sort_dirs, sort_id, sort_direction);
  2217. const $sorted_files = sortIndex( $sort_files, sort_id, sort_direction );
  2218. if (sort_direction === 1) {
  2219. $sorted = $.merge($sorted_dirs,$sorted_files);
  2220. } else {
  2221. $sorted = $.merge($sorted_files,$sorted_dirs);
  2222. }
  2223. } else {
  2224. $sorted = sortIndex( $sort_all, sort_id, sort_direction );
  2225. }
  2226. return $sorted;
  2227. }
  2228. // Sort on click
  2229. function clickSort(id) {
  2230. const el = $(getElById(id)); // id from th sort item = 'sort_by_xxx'
  2231. let sort_direction, query_sort_direction;
  2232. switch(true) {
  2233. case !el.hasClass('selected'): // el is not selected
  2234. $('#sorting_row_1,#sorting_row_2').find('> div').removeClass('up selected');
  2235. el.addClass('selected');
  2236. sort_direction = 1;
  2237. break;
  2238. case el.hasClass('selected'):
  2239. switch(true) {
  2240. case !el.hasClass('up'): el.addClass('up'); sort_direction = -1; break;
  2241. case el.hasClass('up'): el.removeClass('up'); sort_direction = 1; break;
  2242. }
  2243. }
  2244. query_sort_direction = ( sort_direction !== -1 ? 'default' : 'up' );
  2245. setQuery('sort_direction',query_sort_direction);
  2246. // SORT EM!
  2247. let rows = $('#tbody').find('tr'), $sorted_index = sortDirList( rows, id, sort_direction );
  2248. $dir_list_body.empty().append($sorted_index);
  2249. switch(true) {
  2250. case $content_pane.hasClass('has_playlist'): case $content_pane.hasClass('has_filelist'): break;
  2251. case $content_pane.hasClass('has_font_grid'): $('#show_font_grid').click(); break;
  2252. case $content_pane.hasClass('has_image_grid'): $('#show_image_grid').click(); break;
  2253. case $content_pane.attr('data-content') === 'has_grid': $('#grid_btn').click(); break;
  2254. }
  2255. scrollThis('tbody','selected',false); // true = instant scroll
  2256. }
  2257. // Sort on clicking dir_list sort item
  2258. $('#sorting').on('click','.sorting, #play_toggle',function(e) {
  2259. e.preventDefault(); e.stopPropagation();
  2260. switch(true) {
  2261. case $(this).attr('id') === 'play_toggle': toggleAllChecked(); break; // toggle media checkboxes
  2262. case !$(this).hasClass('disabled'): clickSort( $(this).attr('id') ); break; // sort dir_list
  2263. }
  2264. });
  2265. // Sort Menu: click the list header that contains the selected menu text.
  2266. $('#sort_menu').on('click','li:not(.disabled)',function(e) {
  2267. e.preventDefault(); e.stopPropagation();
  2268. $('#sorting div[id="sort_by_'+ $(this).attr('id') +'"]').click(); // click corresponding sidebar sort item
  2269. });
  2270. // ***** END SORTING ***** //
  2271. // ***** END BASIC UI FUNCTIONS ***** //
  2272.  
  2273. // ***** CONTENT PANE ***** //
  2274. // Focus Sidebar
  2275. function focusSidebar() {
  2276. document.activeElement.blur();
  2277. closeMenus();
  2278. sendMessage('iframe','blur_iframe');
  2279. $('body').removeClass('faded focus_content').find('#sidebar').focus();
  2280. }
  2281. // Focus content
  2282. function focusContent(id,e) {
  2283. $('body').addClass('focus_content');
  2284. closeMenus();
  2285. switch(true) {
  2286. case $content_pane.attr('data-content') === 'has_font': $('#specimen').focus(); break;
  2287. case document.activeElement.id.toLowerCase() === 'text_preview' && getFocusableEls('#text_preview').length > 0: getFocusableEls('#text_preview').first().focus(); break;
  2288. case $content_pane.attr('data-content') === 'has_text_editor': focusTextEditorPanes(); break; // focus text editor
  2289. case ( /has_text|has_markdown|has_code|has_htm|has_dir|has_app/.test($content_pane.attr('data-content') ) ): // no break; focus iframe text editor
  2290. case $content_iframe.hasClass('has_content'): // focus iframe and/or iframe text editor
  2291. $('#content_iframe').focus(); if ( e !== undefined && e.shiftKey ) { sendMessage('iframe','shift_focus_iframe'); } else { sendMessage('iframe','focus_iframe'); } break;
  2292. case ( /has_video|has_pdf/.test($content_pane.attr('data-content') ) ): // no break; video, pdf
  2293. case $content_pane.attr('data-content') === undefined: // no break; data-content undefined
  2294. case $('body').hasClass('has_playlist'): $('body').removeClass('focus_content'); break; // don't focus content in this and the above two cases
  2295. case $content_pane.attr('data-content') === 'has_grid': $('#content_grid').focus(); break; // grids
  2296. case $content_pane.attr('data-content') === 'has_image': $('#content_image').focus(); break; // images
  2297. case id !== undefined: document.getElementById(id).focus(); break;
  2298. }
  2299. }
  2300. // get focusable elements
  2301. function getFocusableEls(id) {
  2302. let focusableEls = ( id !== undefined ? $(id).find('a,button,input,select,textarea,div[contenteditable]').filter(':visible') : $('a,button,input,select,textarea,div[contenteditable]').filter(':visible') );
  2303. return focusableEls;
  2304. }
  2305. // focus focusable elements in iframe files or text editor preview
  2306. function focusFocusableEls(e,id) {
  2307. e.preventDefault();
  2308. let els = Array.from(getFocusableEls(id));
  2309. switch(true) {
  2310. case e.shiftKey: // tab + shift: focus prev
  2311. switch(true) {
  2312. case els.indexOf(document.activeElement) === 0: sendMessage('top','focus_sidebar'); break;
  2313. case !els.includes(document.activeElement): els[els.length - 1].focus(); break; // if nothing focused...focus last el
  2314. default: els[els.indexOf(document.activeElement) - 1].focus(); // else focus previous
  2315. }
  2316. break;
  2317. case document.activeElement === els[els.length - 1]: sendMessage('top','focus_sidebar'); break; // if last focussable el, focus sidebar
  2318. default: els[els.indexOf(document.activeElement) + 1].focus(); // tab: select next focusable element
  2319. }
  2320. }
  2321. // focus button (for warnings)
  2322. function focusButton(id) { let el = document.getElementById(id); el.classList.add('focus'); el.focus(); }
  2323. //***** SHOW INDIVIDUAL CONTENT TYPES *****//
  2324. // Show Grid
  2325. function showGrid(id) {
  2326. if ( id !== undefined ) { makeGrids(id); } // initial make grid items; otherwise, just unhide existing grid (see below)
  2327. closeContent();
  2328. const selected_ID = $('#tbody').find('.selected.image, .selected.font').attr('id');
  2329. $('#grid_btn').addClass('has_grid');
  2330. $('#content_pane').removeClass('has_hidden_grid').attr('data-content','has_grid').find('div[data-id="'+ selected_ID +'"]').addClass('selected').siblings().removeClass('selected hovered');
  2331. focusContent();
  2332. }
  2333. // Hide Grid
  2334. function hideGrid() { if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_pane.removeAttr('data-content').addClass('has_hidden_grid'); } }
  2335. // Show Hidden Grid
  2336. function showHiddenGrid() { if ( $content_pane.hasClass('has_hidden_grid') ) { showGrid(); } }
  2337. // Close Grid (remove grid elements, etc.)
  2338. function closeGrid() {
  2339. $('#grid_btn').removeClass('has_grid');
  2340. if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_pane.removeAttr('data-content'); }
  2341. $content_pane.removeClass('has_image_grid has_font_grid').find('#content_grid').attr('style','').empty();
  2342. }
  2343. // Show Text Editor
  2344. function showTextEditor() {
  2345. switch(true) {
  2346. case $content_pane.attr('data-content') === 'has_text_editor': // hide open text editor
  2347. hideTextEditor();
  2348. break;
  2349. case !$body.hasClass('has_text_editor_UI'): // add the text editor UI if loading text editor for first time
  2350. setUpTextEditorUI();
  2351. default: // show editor
  2352. closeContent();
  2353. $content_pane.removeClass('has_hidden_text_editor').attr('data-content','has_text_editor'); // empty title
  2354. $('#text_editor_row').addClass('has_text_editor');
  2355. focusContent();
  2356. }
  2357. }
  2358. // Hide Text Editor
  2359. function hideTextEditor() { if ( $content_pane.attr('data-content') === 'has_text_editor' ) { $content_pane.removeAttr('data-content').addClass('has_hidden_text_editor'); } }
  2360. // Show Hidden Text Editor
  2361. function showHiddenTextEditor() { if ( $content_pane.hasClass('has_hidden_text_editor') ) { showTextEditor(); } }
  2362. // showIndexSource();
  2363.  
  2364. // Show Audio
  2365. function showAudio(id,iframe_audio_link) {
  2366. closeMedia('video');
  2367. let row, link, title;
  2368. if ( id === 'content_iframe_file' ) { // clicked iframe audio files
  2369. link = decodeURIComponentSafe(iframe_audio_link);
  2370. title = link.slice(link.lastIndexOf('/') + 1);
  2371. $content_pane.addClass('has_audio has_iframe_audio');
  2372. } else { // dir_list audio files
  2373. row = getElById(id);
  2374. row.addClass('playing selected').siblings('.media').removeClass('playing selected');
  2375. link = row.find('a').attr('href');
  2376. title = row.find('.name').find('.tbody_row_cell_name_a_span').text();
  2377. $content_pane.addClass('has_audio').removeClass('has_iframe_audio');
  2378. }
  2379. $audio_player.attr('src', link );
  2380. $('#content_audio_title').find('span').empty().text( title );
  2381. $('#content_audio_playlist').removeClass('has_content');
  2382. }
  2383. // Show Font (and create font items) row
  2384. function setContentFontSource(id,bool,sheet,link) { // bool = true if for show font grid, sheet defined at fontGridItems(); link = from previewed directory
  2385. let font_styles = $font_styles.sheet;
  2386. const font_family = ( link !== undefined ? link.slice(link.lastIndexOf('/') + 1,link.lastIndexOf('.')) : getElById(id).find('.name').attr('data-name') );
  2387. const font_url = ( link !== undefined ? link : getElById(id).find('a').attr('href') );
  2388. if ( bool === false ) { // just show selected font
  2389. if ( font_styles.cssRules.length > 0 ) { font_styles.deleteRule(0); } // delete previous @font-face rule
  2390. font_styles.insertRule('@font-face { font-family: "'+ font_family +'"; src: url("'+ font_url +'"); }');
  2391. $content_font.css({ 'font-family':'"'+ font_family +'"' }); // set content font style
  2392. } else { // else make font grid items
  2393. const $font_grid_item_el = $('<div class="font_grid_item border_top_x background_color_EE_22" data-id="'+ id +'"></div>');
  2394. let $grid_item = $font_grid_item_el.clone();
  2395. const display_name = font_family;
  2396. sheet.insertRule('@font-face { font-family: "'+ font_family +'"; src: url("'+ font_url +'"); }');
  2397. $grid_item.append('<p class="text_color_111">'+ display_name.toUpperCase() +'</p><h2 style=\'font-family: "'+ font_family +'"\'; ><a class="text_color_111" href="'+ font_url +'">'+ display_name.slice(0,font_family.lastIndexOf('.')) +'</a></h2>' );
  2398. return $grid_item;
  2399. }
  2400. }
  2401. // OPen Font File (req: opentype.js font parsing)
  2402. $('#open_font_label').on('click',function(e) { e.stopPropagation(); });
  2403. // Open font
  2404. $('#menu').on('change','#open_font',function(e) { $('body').removeClass('has_menu faded'); openFile(e,'font'); });
  2405. // Open Font File
  2406. function openFontFile(files,reader) {
  2407. closeContent();
  2408. hideGrid();
  2409. $content_pane.attr('data-content','has_font_file');
  2410. $content_font.addClass('has_content');
  2411. parseFont(reader.result);
  2412. $('#title span').html( files.name );
  2413. $('#open_font').val(''); // reset input to allow same font to be reopened immediately after closing.
  2414. focusContent();
  2415. }
  2416. // Parse font (req opentype.js)
  2417. function parseFont(fontblob) {
  2418. let font = window.opentype.parse(fontblob);
  2419. getFontInfo(font);
  2420. let $glyphs_container = $('#glyphs_container');
  2421. $glyphs_container.empty();
  2422. let $glyph_container_el = $('<div class="glyph_container background_color_EE_22 border_right_x border_bottom_x"></div>');
  2423. let $glyph_canvas_el = $('<canvas class="glyph invert" width="120" height="120"></canvas>');
  2424. let $glyph_info_el = $('<div class="glyph_info text_color_333"></div>');
  2425. let $glyph_viewer = $('#glyph_viewer');
  2426. $glyph_viewer.data('data-font-name',font.names.fullName.en);
  2427. let glyphs = font.glyphs;
  2428. $content_font.data('data-glyphs',glyphs); // add glyphs data to $content_font
  2429. // Draw glyphs
  2430. for ( let i = 0; i < glyphs.length; i++ ) {
  2431. let glyph = glyphs.glyphs[i];
  2432. // Glyph width
  2433. let bounding_box = glyph.getBoundingBox(), glyph_width = bounding_box.x2 - bounding_box.x1, context_X = (60 - glyph_width/24);
  2434. // Add glyph info and append elements
  2435. let glyph_unicode = ( glyph.unicode !== undefined ? '#'+ glyph.unicode : glyph.unicode );
  2436. let $glyph_container = $glyph_container_el.clone(), $glyph_canvas = $glyph_canvas_el.clone(), $glyph_info = $glyph_info_el.clone();
  2437. $glyph_info.text(glyph.index +': '+ glyph.name +', '+ glyph_unicode);
  2438. $glyph_container.attr('id','glyph_container_'+ glyph.index ).attr('data-id','glyph_container_'+ glyph.index);
  2439. $glyph_container.append( $glyph_canvas_el.clone().attr('id','glyph_'+ glyph.index ) ).append($glyph_info);
  2440. $glyphs_container.append( $glyph_container );
  2441. // Draw glyph
  2442. let this_glyph = document.getElementById('glyph_'+ glyph.index);
  2443. $(this_glyph).data('contextX',context_X);
  2444. let context = this_glyph.getContext('2d');
  2445. glyph.draw(context, context_X, 84, 72);
  2446. }
  2447. }
  2448. // Get font info
  2449. function getFontInfo(font) {
  2450. let font_names = font.names;
  2451. let $font_info = $('<table id="font_info" class="background_color_C0_40"><thead><tr><th colspan=2 class="border_top text_color_111">FONT INFO: '+ font.names.fullName.en.toUpperCase() +'</th></tr></thead><tbody id="font_info_body" class="background_color_D0_50 border_top"></tbody></table>');
  2452. for ( let name in font_names ) {
  2453. let value = font_names[name].en;
  2454. if ( name.endsWith('URL') ) {
  2455. let href = value;
  2456. if ( !value.startsWith('http') ) { href = 'http://'+ value; } // in case url without protocol is used
  2457. value = '<a class="text_color_111" href="'+ href +'" target="_blank">'+ value +'</a>';
  2458. }
  2459. $font_info.find('tbody')
  2460. .append('<tr><td class="font_info_name border_right border_bottom text_color_111">'+ name +': </td><td class="font_info_value border_bottom text_color_111">'+ value +'</td></tr>');
  2461. }
  2462. let num_glyphs = font.numGlyphs; // glyph count
  2463. $font_info.find('tbody')
  2464. .append('<tr><td class="font_info_name border_bottom border_right text_color_111">numGlyphs: </td><td class="font_info_value border_bottom text_color_111">'+ num_glyphs +'</td></tr>');
  2465. $content_font.find('#font_viewer').prepend($font_info);
  2466. }
  2467. // Show glyph viewer
  2468. $content_font.on('click','.glyph',function(e) {
  2469. e.stopPropagation();
  2470. let id = $(this).attr('id');
  2471. $(this).parent('div').addClass('selected').siblings().removeClass('selected');
  2472. showFontGlyph(id);
  2473. });
  2474. function showFontGlyph(id) {
  2475. let $glyph_viewer = $('#glyph_viewer');
  2476. let glyphX = $('#'+ id).data('contextX');
  2477. id = id.slice(id.lastIndexOf('_') + 1); // convert id to number only
  2478. let glyphs = $content_font.data('data-glyphs'); // get glyphs
  2479. let glyph = glyphs.get(id);
  2480. let glyph_name = glyph.name;
  2481. let glyph_path = glyph.getPath(glyphX,84,72);
  2482. let glyph_SVG = glyph_path.toSVG().replace(/"/g,'\'');
  2483. // set the background SVG image:
  2484. let SVG_prefix = '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>';
  2485. $glyph_viewer.show().css({'background-image': SVG_prefix + glyph_SVG +'</g></svg>")'});
  2486. $glyph_viewer.data('data-raw-svg',glyph_SVG).data('data-glyph-name',glyph_name).find('#glyph_viewer_info div').text(id +': '+ glyph_name +', #'+ glyph.unicode ); // for saving SVG
  2487. $content_pane.attr('data-content','has_glyph');
  2488. }
  2489. // Save glyph svg
  2490. function saveGlyph() {
  2491. let file_name = $('#glyph_viewer').data('data-font-name') +'—'+ $('#glyph_viewer').data('data-glyph-name') +'.svg';
  2492. 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>';
  2493. let data_suffix = '</g></svg>';
  2494. let data = data_prefix + $('#glyph_viewer').data('data-raw-svg') + data_suffix;
  2495. saveFile(data,'image/svg+xml',file_name);
  2496. }
  2497. $content_font.on('click','#save_svg',function(e) { e.stopPropagation(); saveGlyph(); });
  2498. // END OPEN FONT FILE Operations
  2499.  
  2500. // Create new #content_pdf
  2501. function setupContentPDF() {
  2502. let $embed = '<embed id="content_pdf" class="content" name="plugin" tabindex="0" data-kind="pdf"></embed>';
  2503. $('#content_pdf').remove();
  2504. $content_image_container.after($embed);
  2505. }
  2506. // OPEN LINK FILES (webloc, url)
  2507. function openLink(ext) {
  2508. let link;
  2509. let content = ( $protocol === 'file:' ? $('#iframe_body pre').text() : document.getElementsByTagName('iframe')[0].contentWindow.document.children[0].children[1].innerHTML );
  2510. switch(true) {
  2511. case content === undefined: break;
  2512. case ext === 'webloc': case 'inetloc': link = content.split('<string>')[1].split('</string>')[0]; break;
  2513. case ext === 'url': link = content.split('URL=')[1].split('\n')[0];
  2514. }
  2515. let url = ( link.startsWith('file') ? link : newURL(link) );
  2516. switch(true) {
  2517. case url === undefined: break;
  2518. case link.startsWith('file') && window.location.protocol !== 'file:': // local files and links
  2519. $body.addClass('has_warning').find('#warnings_container').addClass('warning_local_file'); $('#warning_btn_ok').focus();
  2520. break;
  2521. case link.startsWith('file') && window.location.protocol === 'file:': // local files and links
  2522. case url.origin === window.location.origin: // same origin
  2523. sendMessage('top','show_content','',[link,getItemKind(getItemExt(link))]);
  2524. break;
  2525. default:
  2526. window.open(link,'_blank'); // open non-local links in new tab
  2527. }
  2528. }
  2529. // END SHOW INDIVIDUAL CONTENT TYPES
  2530.  
  2531. // ***** MAIN SHOW CONTENT FUNCTIONS ***** //
  2532. // Set Content Pane classes
  2533. function setContentClasses(id, kind, content_el) {
  2534. switch(true) { // add loaded or playing classes
  2535. case kind === 'audio': case kind === 'video':
  2536. getElById(id).addClass('playing').siblings('.media').removeClass('playing');
  2537. break;
  2538. default: // non-media items
  2539. getElById(id).addClass('loaded').siblings().removeClass('loaded');
  2540. }
  2541. switch(kind) {
  2542. case 'audio':
  2543. if ( id === 'content_iframe_file' ) { $content_pane.addClass('has_iframe_audio'); } else { $content_pane.addClass('has_audio'); }
  2544. break;
  2545. case 'app': case 'dir':
  2546. if ( id === 'content_iframe_file' ) { $content_pane.attr('data-content','has_dir').addClass('has_iframe_dir'); }
  2547. else { $content_pane.attr('data-content','has_dir').addClass('has_dir').removeClass('has_iframe_dir has_iframe_file'); }
  2548. break;
  2549. default:
  2550. if ( id === 'content_iframe_file' ) { $content_pane.removeClass('has_dir').addClass('has_iframe_file').attr('data-content','has_'+ kind);}
  2551. else { $content_pane.attr('data-content','has_'+ kind).removeClass('has_dir has_iframe_dir has_iframe_file'); }
  2552. }
  2553. $('#content_'+ content_el).addClass('has_content');
  2554. }
  2555. // get query string for various content kinds
  2556. function makeSearchParams(params) {
  2557. let query_str = new URLSearchParams();
  2558. for ( let param of params ) { query_str.append(param,getQuery(param)); }
  2559. return query_str;
  2560. }
  2561. function getLinkQueries(kind) {
  2562. let query_str, params;
  2563. switch(true) {
  2564. case kind === 'view_directory_source':
  2565. query_str = '?&view_directory_source=true';
  2566. break;
  2567. case kind === 'pdf': // pdfs
  2568. query_str = '#view=fitB&scrollbar=1&toolbar=1&navpanes=1';
  2569. break;
  2570. case kind === 'code': case kind === 'markdown': case kind === 'text': // editable text files
  2571. params = ['split_view','default_text_view','theme','editor_theme','disable_text_editing'];
  2572. query_str = '?'+ makeSearchParams(params).toString();
  2573. break;
  2574. case kind === 'app': case kind === 'dir': // apps and directories
  2575. params = ['sort_by','sort_direction','show_numbers','use_custom_icons','show_invisibles','hide_ignored_items','ignore_ignored_items','alternate_background','theme'];
  2576. query_str = '?'+ makeSearchParams(params).toString();
  2577. }
  2578. return query_str;
  2579. }
  2580. // CREATE LINKS FOR VARIOUS CONTENT KINDS
  2581. function setContentSources(id, kind, link, content_el) {
  2582. switch(true) { // make source link for content elements; default is link
  2583. case kind === 'font':
  2584. setContentFontSource(id,false,'',link);
  2585. break;
  2586. case kind === 'view_directory_source': // make link for viewing directory source
  2587. link = current_location + '?&view_directory_source=true';
  2588. break;
  2589. }
  2590. $('#content_'+ content_el).attr('src',link); // set source for content element
  2591. }
  2592. // GET CONTENT SRC AND NAME
  2593. function getContentLinkAndName(kind,link) {
  2594. let prepped_link = decodeURIComponentSafe( link.split('/?')[0] ); // remove protocol and query string, if any
  2595. prepped_link = prepped_link.split('://').reverse()[0];
  2596. let prepped_name;// = prepped_link.split('/').reverse();
  2597. if ( /app|dir/.test(kind) ) { (
  2598. !prepped_link.endsWith('/') ? prepped_link += '/' : null ); prepped_name = prepped_link.split('/').reverse()[1] + '/';
  2599. } else {
  2600. prepped_name = prepped_link.split('/').reverse()[0];
  2601. }
  2602. return [prepped_link,prepped_name];
  2603. }
  2604. // SET CONTENT TITLE
  2605. function setContentTitle(kind,link) {
  2606. let $title = $('#title span'), title_text;
  2607. let $selected = $('#tbody tr.selected'), selected_items, selected_link, selected_name, content_items, content_link, content_name, iframe_items, iframe_link, iframe_name;
  2608. if ( $selected.length === 1 ) { // selected sidebar item
  2609. selected_items = getContentLinkAndName($selected.attr('data-kind'),$selected.find('a').attr('href'));
  2610. selected_link = selected_items[0];
  2611. selected_name = selected_items[1];
  2612. }
  2613. let $content_el = $('#content_container .has_content:not(#content_iframe)');
  2614. if ( $content_el.length === 1 ) { // content element source
  2615. content_items = getContentLinkAndName('',$content_el.attr('src') );
  2616. content_link = content_items[0];
  2617. content_name = content_items[1];
  2618. }
  2619. if ( link !== undefined ) { // iframe source
  2620. iframe_items = getContentLinkAndName(kind,link);
  2621. iframe_link = iframe_items[0];
  2622. iframe_name = iframe_items[1];
  2623. }
  2624. switch(true) {
  2625. case iframe_link === undefined && content_link === undefined: title_text = selected_name; break; // title for selected sidebar item
  2626. case iframe_link === undefined && selected_link !== content_link: title_text = content_link; break;
  2627. case selected_link === iframe_link: title_text = iframe_name; break;
  2628. case kind === 'image' && $('body').hasClass('autoload_media') && $('body').hasClass('has_audio') && $('body').hasClass('has_images'): // autoload media images
  2629. case selected_link === content_link: title_text = content_name; break;
  2630. case selected_link !== iframe_link: title_text = window.location.protocol + '//'+ iframe_link; break; // --> if not file://, need to use full path
  2631. }
  2632. title_text = title_text.split('/').join('/<wbr>').split('_').join('_<wbr>'); // allow nice breaks in title
  2633. if ( kind === 'image' ) { setImageDimensions(); } // set image dimensions
  2634. $title.empty().html(decodeURIComponentSafe(title_text)); // set text
  2635. }
  2636. // Get Image Dimensions
  2637. function getImageDimensions(link, callback) {
  2638. if ( link !== undefined ) {
  2639. let img = new Image();
  2640. img.src = link;
  2641. img.onload = function() { callback( this.width, this.height ); };
  2642. }
  2643. }
  2644. function setImageDimensions() {
  2645. if ( window.top !== window.self ) {
  2646. return;
  2647. } else if ( $content_pane.attr('data-content') === 'has_image' ) {
  2648. let link = $content_image.attr('src');
  2649. getImageDimensions( link, function( width, height ) {
  2650. $('#title span').attr('data-after',' (' + width + 'px × ' + height + 'px) ('+ ( ($content_image.width()/width)*100 ).toFixed(1) +'%)');
  2651. });
  2652. } else {
  2653. $('#title span').removeAttr('data-after'); // remove image dimensions
  2654. }
  2655. }
  2656. // SHOW IFRAME CONTENT called when iframe loads
  2657. function showIframeContent(args) {
  2658. let link = args[0]; // full link with querys
  2659. let kind = $content_iframe.attr('data-kind');
  2660. $content_pane.attr('data-loaded','loaded'); // show iframe, hide loading spinner
  2661. switch(true) { // make source link for content elements; default is link
  2662. case kind === 'app': case kind === 'dir':
  2663. $content_pane.attr('data-content','has_dir');//.addClass('has_iframe_dir').removeClass('has_iframe_file');
  2664. break;
  2665. default:
  2666. $content_pane.attr('data-content','has_'+kind);//.removeClass('has_iframe_dir');//.removeClass('has_iframe_file');
  2667. }
  2668. setContentTitle(kind,link);
  2669. }
  2670. // SHOW OTHER CONTENT
  2671. function showContent(id, args) { // show any content on row click; last two arguments are only from clicking iframe links; args = [link,kind]
  2672. let row, link, kind, name = '', src = $content_iframe.attr('src');
  2673. switch(true) { // set vars and content_iframe attributes for iframe content or sidebar content
  2674. case id === 'open_sidebar_in_content_pane':
  2675. case id === 'content_iframe_dir': // prep for iframe dirs
  2676. $('body').addClass('focus_content')
  2677. link = args[0]; kind = args[1];
  2678. $content_pane.addClass('has_iframe_dir').removeClass('has_iframe_file');
  2679. $content_iframe.removeAttr('data-iframe_file_src');
  2680. break
  2681. case id === 'content_iframe_file': // prep for iframe files
  2682. $('body').addClass('focus_content')
  2683. link = args[0]; kind = args[1];
  2684. if ( kind === 'audio' ) {
  2685. $content_pane.addClass('has_iframe_audio');
  2686. } else {
  2687. $content_pane.addClass('has_iframe_file');
  2688. $content_iframe.attr('data-iframe_file_src',$content_iframe.attr('src')); //
  2689. }
  2690. break;
  2691. case id !== '': // show content from sidebar items or iframe items (from above) and get link, kind
  2692. row = getElById(id); link = row.find('a').attr('href'); kind = row.attr('data-kind');
  2693. break;
  2694. }
  2695. let content_el = ( ['audio','font','image','pdf','video'].includes(kind) ? kind : 'iframe' );
  2696. switch(true) {
  2697. case window.top !== window.self:
  2698. break; // don't attempt to show content for iframe items when navigating
  2699. case row !== undefined && row.hasClass('ignored') && getQuery('ignore_ignored_items') === 'false': // try to open ignored files if they are not to be ignored
  2700. window.location = row.find('a').attr('href');
  2701. break;
  2702. case row !== undefined && row.hasClass('ignored') && getQuery('ignore_ignored_items') === 'true': // don't open ignored files if they are to be ignored
  2703. // case row.hasClass('dir') && row.hasClass('invisible') && row.hasClass('ignored'):
  2704. closeContent();
  2705. setContentTitle();
  2706. $content_pane.attr('data-content','has_ignored');
  2707. break;
  2708. case kind === 'link':
  2709. closeContent();
  2710. $content_pane.attr('data-loaded','loaded');
  2711. $content_iframe.addClass('has_content').attr('src',link).attr('data-kind',kind);
  2712. break;
  2713. case id === 'text_editor' || id === 'text_editor_row': showTextEditor(); break; // show the text editor
  2714. case id === 'grid_btn' || id === 'show_image_grid' || id === 'show_font_grid': showGrid(id); break; // show grid
  2715. case kind === 'audio': showAudio(id,link); break; // show audio
  2716. case kind === 'video': closeMedia('audio'); // close audio when showing video ???? test if needed?
  2717. case ['font','image','pdf','video'].includes(kind):
  2718. closeContent(); // Close all open content
  2719. setContentSources(id, kind, link, content_el); // Set the src attribute for the content element
  2720. setContentClasses(id, kind, content_el); // Add "data=has_<kind>" to content_pane; test id for content_iframe
  2721. setContentTitle(kind); // Set the title for non-iframe content
  2722. break;
  2723. // case ( /code|text|markdown/.test('kind') ): // send stored text selection data to iframe --> NOT IMPLEMENTED YET
  2724. // sendMessage('iframe','get_text_selection','',[row.attr('data-selection_start'),row.attr('data-selection_end')]);
  2725. // break;
  2726. case id === 'view_directory_source':
  2727. kind = 'view_directory_source'; link = args[0]; // no break;
  2728. case kind === 'dir' && id !== 'content_iframe_dir':
  2729. case kind === 'app' && id !== 'content_iframe_dir':
  2730. link = link + getLinkQueries(kind); // set link for sidebar directories; no break
  2731. case content_el === 'iframe': // set iframe source and hide until iframe loaded
  2732. closeContent(); // Close all open content
  2733. $content_pane.attr('data-loaded','unloaded'); // hide iframe until loaded, show loading spinner
  2734. $content_iframe.addClass('has_content').attr('src',link).attr('data-kind',kind);
  2735. break;
  2736. }
  2737. if ( $('body').hasClass('focus_content') || id === 'content_iframe' ) { sendMessage('iframe','focus_iframe'); }
  2738. }
  2739. // Show source of current sidebar dir in content pane
  2740. function showDirectorySource() {
  2741. switch(true) {
  2742. case $body.hasClass('has_directory_source'): $('#close_btn').click(); break; // close if open
  2743. default: $body.addClass('has_directory_source'); showContent('view_directory_source',[current_location]);
  2744. }
  2745. }
  2746. $('#view_directory_source').on('click',function(e) { e.stopPropagation(); showWarning( 'showDirectorySource' ); }); // toggle show directory source
  2747. // Show current sidebar dir in content pane
  2748. function openSidebarInContentPane() { showContent('open_sidebar_in_content_pane',[current_location,'dir']); focusContent(); }
  2749. $('#open_in_content_pane').on('click',function() { showWarning( 'openSidebarInContentPane' ); });
  2750. // Open Previewed Dir in Sidebar (@ receiveMessage() )
  2751. function openIframeDirInSidebar(args) {
  2752. let query_str = getQueryPrefs();
  2753. if (query_str.has('selected') ) { query_str.delete('selected'); }
  2754. if (query_str.has('history') ) { query_str.delete('history'); }
  2755. window.location = args.slice(0,args.lastIndexOf('/?')+1) +'?'+ query_str.toString();
  2756. }
  2757. //***** CLOSE CONTENT (Close button or Cmd/Ctrl + W) *****//
  2758. // Close Audio/Video
  2759. function closeMedia(type) { // type === audio || video
  2760. let $media_el = ( type === 'audio' ? $audio_player : $content_video );
  2761. $media_el.trigger('pause').removeAttr('src');
  2762. switch(true) {
  2763. case type === 'audio':
  2764. $('tr.audio.playing').removeClass('playing');
  2765. $('#content_audio_title span').empty();
  2766. $('#content_audio_playlist').removeClass('has_content');
  2767. if ( $content_pane.hasClass('has_iframe_audio') ) {
  2768. focusContent('content_iframe');
  2769. }
  2770. $content_pane.removeClass('has_audio has_iframe_audio');
  2771. sendMessage('iframe','close_iframe_audio');
  2772. break;
  2773. case type === 'video' && $('#content_pane').attr('data-content') === 'has_video':
  2774. $('tr.video.playing').removeClass('playing');
  2775. $('#content_pane').removeAttr('data-content').find('#content_video').removeClass('has_content');
  2776. setContentTitle(type);
  2777. break;
  2778. }
  2779. }
  2780. $('#close_audio').on('click',function(e) { e.stopPropagation(); closeMedia('audio'); }); // Close Audio button click
  2781. // Close Playlist
  2782. function closePlaylist() {
  2783. $('#tbody').empty().html($('#tbody').data('dir_list')); // restore original dir_list
  2784. $body.removeClass('has_playlist has_filelist has_media has_audio has_video has_fonts has_images')
  2785. .addClass( $('#tbody').data('data_classes').join(' ') ).find('#sort_by_date,li#date').removeClass('disabled'); // restore orignal body "has" classes
  2786. $('head').find('title').text('Index of: '+ current_location); // change window title back to default
  2787. $('#parents_dir_nav').find('> div').empty().html(current_dir_path);
  2788. $dir_list.find('#stats').html(getIndexStats($dir_list.find('#tbody tr')));
  2789. if ( $('#tbody').find('.media').length < 1 ) { $body.removeClass('has_media has_audio has_video'); }
  2790. }
  2791. // Close index source preview
  2792. function closeIndexSource() {
  2793. $body.removeClass('has_directory_source');
  2794. $('.dir.loaded').click();
  2795. }
  2796. // Close opened font file
  2797. function closeFontFile() {
  2798. $body.removeClass('faded focus_content');
  2799. $content_pane.removeAttr('data-content').find('#glyphs_container').empty();
  2800. $('#font_viewer').remove().end().append(ContentFontViewer());
  2801. $('tr.loaded:not(.audio)').click();
  2802. }
  2803. // Close glyph
  2804. function closeGlyph() {
  2805. $('#glyph_viewer').hide().attr('style','');
  2806. $content_pane.attr('data-content','has_font_file');
  2807. $content_font.addClass('has_content');
  2808. scrollThis('glyphs_container','selected');
  2809. }
  2810. // Close all .content elements before opening any new .content from sidebar; leave grid and text editor hidden.
  2811. function closeContent() {
  2812. switch(true) {
  2813. case window.top !== window.self:
  2814. break;
  2815. default: // remove sources, data-content, styles, and classes from #content_pane
  2816. $('#title span').removeAttr('data-after').empty(); // empty content title
  2817. hideGrid(); hideTextEditor(); // hide grid and texteditor
  2818. $content_pane.removeClass('has_zoom_image has_scaled_image').removeAttr('data-content').removeAttr('data-loaded'); // remove content_pane classes and data
  2819. $content_pane.find('.has_content').removeClass('has_content').removeAttr('src').removeAttr('style'); // remove .content classes, data, and attributes
  2820. $content_font.find('#font_viewer').remove().end().append(ContentFontViewer());
  2821. closeMedia('video');
  2822. setupContentPDF();
  2823. $('.playlist_entry_container').removeClass('has_content').find('textarea').val('');
  2824. }
  2825. }
  2826. //////// NEW CLOSE BUTTON FUNCTION: replaces OLDcloseContent()
  2827. function closeButton() {
  2828. switch(true) {
  2829. // CLOSE GRIDS & TEXT EDITOR
  2830. case $content_pane.attr('data-content') === 'has_grid': // close grid
  2831. closeGrid();
  2832. if ( $content_pane.hasClass('has_hidden_text_editor') ) { showHiddenTextEditor(); } else { focusSidebar(); }
  2833. break;
  2834. case $content_pane.attr('data-content') === 'has_text_editor': // hide text editor
  2835. hideTextEditor();
  2836. if ( $content_pane.hasClass('has_hidden_grid') ) { showHiddenGrid(); } else { focusSidebar(); }
  2837. break;
  2838. case $body.hasClass('iframe_edited'): // close edited iframe file
  2839. sendMessage('iframe','unloading');
  2840. break;
  2841. // CLOSE FILES AND DIRECTORIES
  2842. case $content_pane.hasClass('has_iframe_file') && $content_pane.hasClass('has_iframe_dir'): { // close iframe file and reopen navigated container directory
  2843. let kind = ( $content_pane.attr('data-content') !== 'has_dir' ? 'htm' : 'dir' ); // needed to set correct title
  2844. showContent('content_iframe_dir',[$content_iframe.attr('data-iframe_file_src'),'dir']);
  2845. sendMessage('iframe','focus_iframe');
  2846. break;
  2847. }
  2848. case $content_pane.hasClass('has_iframe_file') && !$content_pane.hasClass('has_iframe_dir'):
  2849. // close iframe file opened from previewed sidebar directory (not navigated directory) and reopen selected sidebar directory
  2850. $content_pane.removeClass('has_iframe_file').removeAttr('data-content');
  2851. // no break;
  2852. case $content_pane.hasClass('has_iframe_dir'): // close navigated iframe directory and reopen selected sidebar directory
  2853. $content_pane.removeClass('has_iframe_dir');
  2854. clickRow($('#tbody tr.selected').attr('id')); // click selected sidebar item
  2855. sendMessage('iframe','focus_iframe');
  2856. break;
  2857. case $content_pane.attr('data-content') === 'has_dir': // close selected sidebar directory
  2858. $('tr.selected:not(.audio)').removeClass('selected loaded');
  2859. $content_pane.removeAttr('data-content').removeAttr('data-loaded'); //
  2860. $content_iframe.removeAttr('src'); // remove iframe src
  2861. $('#title span').empty(); // empty content title
  2862. focusSidebar();
  2863. break;
  2864. case $body.hasClass('has_directory_source'): // close directory source
  2865. closeIndexSource();
  2866. break;
  2867. // CLOSE FONTS & GLYPHS
  2868. case $content_pane.attr('data-content') === 'has_glyph': // close glyph
  2869. showWarning( 'closeGlyph' );
  2870. break;
  2871. case $content_pane.attr('data-content') === 'has_font_file': // close font file preview
  2872. showWarning( 'closeFontFile' );
  2873. break;
  2874. // CLOSE ALL OTHER CONTENT EXCEPT AUDIO
  2875. case $content_pane.attr('data-content') === 'has_ignored': // close ignored content
  2876. case $('#content_container .content').hasClass('has_content'): // close all other content
  2877. case $('#content_image').hasClass('has_content'): // close all other content
  2878. $('tr.selected:not(.audio),tr.loaded:not(.audio)').removeClass('selected loaded');
  2879. $content_iframe.removeAttr('data-iframe_file_src').removeAttr('data-kind');
  2880. closeContent(); // false = show hidden text editor or grid after closing other content
  2881. focusSidebar();
  2882. switch(true) {
  2883. case $content_pane.hasClass('has_hidden_grid'): showGrid(); break;
  2884. case $content_pane.hasClass('has_hidden_text_editor'): showTextEditor(); break;
  2885. }
  2886. break;
  2887. // CLOSE AUDIO & PLAYLISTS
  2888. case $content_pane.hasClass('has_audio'): // close audio
  2889. closeMedia('audio'); focusSidebar();
  2890. break;
  2891. case $body.hasClass('has_playlist'): // close playlist
  2892. case $body.hasClass('has_filelist'): // close filelist
  2893. showWarning( 'closePlaylist' );
  2894. break;
  2895. }
  2896. // show hidden texteditor or grid
  2897. }
  2898. $('#close_btn').on('click', function(e) { e.stopPropagation(); e.preventDefault(); showWarning('closeButton'); $(this).blur(); });
  2899. //***** RESET CONTENT (Reset button or Cmd/Ctrl + R) *****//
  2900. function reloadImage() {
  2901. $content_pane.removeClass('has_zoom_image has_scaled_image').find('#content_image_container,#content_image').removeAttr('style');
  2902. $('#content_image').removeAttr('src');
  2903. $('#tbody').find('.image.selected,.image.loaded').find('a').click(); // clicking allows changed content to be reloaded instead of just being reset
  2904. }
  2905. function resetContent() {
  2906. switch(true) {
  2907. case $content_pane.hasClass('has_audio'): // pause and reset media
  2908. $audio_player.prop('currentTime', 0).trigger('pause');
  2909. reloadImage();
  2910. break;
  2911. case $content_pane.attr('data-content') === 'has_video':
  2912. $content_video.prop('currentTime',0).trigger('pause');
  2913. break;
  2914. }
  2915. switch(true) {
  2916. case $content_pane.attr('data-content') === 'has_grid':
  2917. $('#grid_btn').click();
  2918. break;
  2919. case $content_pane.attr('data-content') === 'has_glyph':
  2920. $('#glyph_viewer').css({'width':'100%','height':'100%','background-size':'100%'}).attr('data-scale',1);
  2921. break;
  2922. case $content_pane.attr('data-content') === 'has_font':
  2923. $content_font.css({'font-size':'1em'});
  2924. break;
  2925. case $body.hasClass('has_playlist'):
  2926. case $content_pane.hasClass('has_audio'):
  2927. case $content_pane.hasClass('has_video'):
  2928. case $content_pane.attr('data-content') === 'has_text_editor':
  2929. break; // don't do anything else for audio, video, text editor, playlist content.
  2930. case $content_pane.attr('data-content') === 'has_image':
  2931. reloadImage();
  2932. break;
  2933. case $content_pane.hasClass('has_iframe') || $content_pane.hasClass('has_dir'):
  2934. case $content_pane.attr('data-content') === 'has_iframe' || $content_pane.attr('data-content') === 'has_dir':
  2935. $('#tbody').find('.selected').find('a').click(); // clicking allows changed content to be reloaded instead of just being reset
  2936. break;
  2937. case $content_pane.attr('data-content') === 'has_ignored':
  2938. case $content_pane.attr('data-content') === undefined:
  2939. window.location = window.location.href;
  2940. break;
  2941. }
  2942. }
  2943. $('#reload_btn').on('click', function(e) { e.stopPropagation(); e.preventDefault(); showWarning('resetContent'); $(this).blur().removeClass('reset'); });
  2944. //**********************//
  2945. //***** NAVIGATION *****//
  2946. function firstRowID(class_name) { return ( $('#tbody').find('> tr:visible').filter(class_name).length ? $('#tbody').find('> tr:visible').filter(class_name).first().attr('id') : null ); }
  2947. function prevRowID(class_name) {
  2948. let $selected, row_ID;
  2949. if ( /audio|video|media/.test(class_name) ) { $selected = $('#tbody').find('> tr.playing'); } else { $selected = $('#tbody').find('> tr.selected'); }
  2950. if ( !$selected.length || !$selected.prevAll('tr:visible:not(.disabled)').filter(class_name).length ) {
  2951. row_ID = $('#tbody').find('> tr:visible:not(.disabled)').filter(class_name).last().attr('id'); // select last item if nothing selected or selected is first item
  2952. } else {
  2953. row_ID = $selected.prevAll('tr:visible:not(.disabled)').filter(class_name).first().attr('id');
  2954. }
  2955. return row_ID;
  2956. }
  2957. function nextRowID(class_name) { // if nothing selected, or if no next row with class_name, return first row with class_name, else return next row with class_name
  2958. let $selected, row_ID;
  2959. if ( /audio|video|media/.test(class_name) ) { $selected = $('tr.playing'); } else { $selected = $('#tbody').find('.selected'); }
  2960. if ( !$selected.length || !$selected.nextAll('tr:visible:not(.disabled)').filter(class_name).length ) {
  2961. row_ID = $('#tbody').find('> tr:visible:not(.disabled)').filter(class_name).first().attr('id'); // select last item if nothing selected or selected is first item
  2962. } else {
  2963. row_ID = $selected.nextAll('tr:visible:not(.disabled)').filter(class_name).first().attr('id');
  2964. }
  2965. return row_ID;
  2966. }
  2967. // get selected row id
  2968. function selectRowID(class_name,key) {
  2969. let id = '';
  2970. switch(key) {
  2971. case 'ArrowUp': id = prevRowID('.dir,.file'); break;
  2972. case 'ArrowLeft': id = prevRowID(class_name); break;
  2973. case 'ArrowRight': id = nextRowID(class_name); break;
  2974. case 'ArrowDown': id = nextRowID('.dir,.file'); break;
  2975. }
  2976. return id;
  2977. }
  2978. // NAVIGATION: Menu Arrow Navigation
  2979. function menuNavigation(key) {
  2980. switch(key) {
  2981. case 'ArrowUp':
  2982. switch(true) {
  2983. case $('#menu').find('.hovered').length > 0: // if submenu visible
  2984. if ( !$('#menu').find('ul li.selected').length ) { // if no menu item selected, select last submenu item
  2985. $('#menu').find('ul li:last-of-type').addClass('selected');
  2986. } else { // else select previous submenu item
  2987. $('#menu').find('ul li.selected').removeClass('selected').prev('li').addClass('selected');
  2988. }
  2989. break;
  2990. case !$('#menu').find('.selected').length:
  2991. $('#menu').find('> li:last-of-type').addClass('selected');
  2992. break; // select last item
  2993. default:
  2994. $('#menu').find('.selected:not(.hovered)').removeClass('selected').prev('li').addClass('selected');
  2995. }
  2996. break;
  2997. case 'ArrowDown':
  2998. switch(true) {
  2999. case $('#menu').find('.hovered').length > 0: // if submenu visible
  3000. if ( !$('#menu').find('ul li.selected').length ) { // if no menu item selected, select first submenu item
  3001. $('#menu').find('ul li:first-of-type').addClass('selected');
  3002. } else { // else select next submenu item
  3003. $('#menu').find('ul li.selected').removeClass('selected').next('li').addClass('selected');
  3004. }
  3005. break;
  3006. case !$('#menu').find('.selected').length:
  3007. $('#menu').find('> li:first-of-type').addClass('selected');
  3008. break;
  3009. default:
  3010. $('#menu').find('.selected:not(.hovered)').removeClass('selected').next('li').addClass('selected');
  3011. }
  3012. break;
  3013. case 'ArrowLeft':
  3014. switch(true) {
  3015. case !$('#menu').find('.selected').length:
  3016. $('#menu').find('> li:last-of-type').addClass('selected');
  3017. break;
  3018. case $('#menu').find('li.selected').hasClass('hovered'):
  3019. $('#menu').find('.hovered').removeClass('hovered'); $('#menu').find('ul li').removeClass('selected');
  3020. break;
  3021. }
  3022. break;
  3023. case 'ArrowRight':
  3024. switch(true) {
  3025. case !$('#menu').find('.selected').length:
  3026. $('#menu').find('> li:first-of-type').addClass('selected');
  3027. break;
  3028. case $('#menu').find('li.selected').hasClass('has_submenu') && !$('.submenu').find('.selected').length:
  3029. $('#menu').find('.selected').addClass('hovered').find('ul li:first-of-type').addClass('selected');
  3030. break;
  3031. }
  3032. break;
  3033. }
  3034. }
  3035. // NAVIGATION: select GRID items by left/right arrow keys @ arrowNavigation();
  3036. function gridNavigation(elId,key) {
  3037. const $container_el = $(elId);
  3038. let $selected = $container_el.find('> .selected');
  3039. let grid_item_id; // = grid item data-id = corresponding dir_list item id.
  3040. let row_length = 0; // $content_grid.hasClass('has_grid') || $('#content_font').hasClass('has_font_grid')
  3041. row_length = ( Math.round( $container_el.innerWidth() / $container_el.find('> div').outerWidth()) - 1 );
  3042. switch(true) {
  3043. case $selected.length === 0: // nothing selected
  3044. switch(true) {
  3045. case key === 'ArrowRight': case key === 'ArrowDown': $container_el.find('> div').first().addClass('selected'); break;
  3046. case key === 'ArrowLeft': case key === 'ArrowUp': $container_el.find('> div').last().addClass('selected'); break;
  3047. }
  3048. grid_item_id = $container_el.find('.selected').attr('data-id');
  3049. break;
  3050. case key === 'ArrowDown':
  3051. switch(true) {
  3052. case $selected.nextAll().eq(row_length).length === 1: grid_item_id = $selected.nextAll().eq(row_length).attr('data-id'); break;
  3053. case $container_el.find('> div').length - 1 === row_length: gridNavigation(elId,'ArrowRight'); break; // for single grid row, select next item
  3054. default: grid_item_id = $container_el.find('> div').first().attr('data-id'); break;
  3055. }
  3056. break;
  3057. case key === 'ArrowUp':
  3058. switch(true) {
  3059. case $selected.prevAll().eq(row_length).length === 1: grid_item_id = $selected.prevAll().eq(row_length).attr('data-id'); break;
  3060. case $container_el.find('> div').length - 1 === row_length: gridNavigation(elId,'ArrowLeft'); break; // for single grid row, select prev item
  3061. default: grid_item_id = $container_el.find('> div').last().attr('data-id'); break;
  3062. }
  3063. break;
  3064. case key === 'ArrowLeft':
  3065. switch(true) {
  3066. case $selected.prev().length === 1: grid_item_id = $selected.prev().attr('data-id'); break;
  3067. default: grid_item_id = $container_el.find('> div').last().attr('data-id'); break;
  3068. }
  3069. break;
  3070. case key === 'ArrowRight':
  3071. switch(true) {
  3072. case $selected.next().length === 1: grid_item_id = $selected.next().attr('data-id'); break;
  3073. default: grid_item_id = $container_el.find('> div').first().attr('data-id'); break;
  3074. }
  3075. break;
  3076. }
  3077. selectThis(grid_item_id);
  3078. switch(true) {
  3079. case $content_pane.attr('data-content') === 'has_glyph': $('.glyph_container.selected .glyph').click(); break; // show the selected glyph
  3080. case $content_pane.hasClass('has_hidden_grid'): $('#content_grid > div[data-id="'+ grid_item_id +'"]').click(); break;
  3081. }
  3082. }
  3083. // NAVIGATION: FONTS and IMAGES by prev/next buttons
  3084. $content_pane.on( 'click','#prev_btn, #next_btn', function(e) {
  3085. e.stopPropagation(); e.preventDefault();
  3086. let key = $(this).attr('id') === 'prev_btn' ? 'ArrowLeft' : 'ArrowRight';
  3087. if ( $content_pane.attr('data-content') === 'has_font_viewer' ) {
  3088. gridNavigation('#glyphs_container',key);
  3089. } else {
  3090. clickRow( selectRowID('.font,.image',key));
  3091. }
  3092. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  3093. });
  3094. // 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
  3095. function playPrevNextTrack(key) {
  3096. switch(true) {
  3097. case $('#audio').attr('src') === 'unknown' && $('tr.selected').hasClass('audio'):
  3098. showAudio( $('tr.selected').attr('id') );
  3099. break;
  3100. case $content_pane.hasClass('has_iframe_audio'): // play track from navigated directory
  3101. sendMessage('iframe','play_prev_next_iframe_audio','',key);
  3102. break;
  3103. case $('.playing').length === 0: // Arrow L/R selects first/last audio file if nothing selected
  3104. clickRow( selectRowID('.media:not(.unchecked)',key) ); playMedia('play');
  3105. break;
  3106. case $('.playing').length === 1: {
  3107. let media_class = ( $('.playing').hasClass('audio') ? '.audio:not(.unchecked)' : '.video:not(.unchecked)' );
  3108. switch(true) {
  3109. case $body.hasClass('shuffle_audio'): { // If shuffle play...
  3110. let trackRowID = $audio_player.data('shufflelist').pop(); // remove track from list
  3111. if ( trackRowID !== undefined ) { // if shuffle list is not empty...
  3112. showAudio( trackRowID ); playMedia('play'); // load media and play
  3113. } else if ( trackRowID === undefined ) { // if end of shufflelist...
  3114. if ( $body.hasClass('loop_audio') ) { // and if loop audio, update the shufflelist and play
  3115. updateShuffleList(); playMedia('play');
  3116. } else { // else just load the first track
  3117. showAudio( firstRowID( media_class ));
  3118. }
  3119. }
  3120. break;
  3121. }
  3122. case $(media_class).filter('.selected').length === 1 && !$('.media.selected').hasClass('playing'): // else if there is another media file selected, play it next:
  3123. showAudio( $('.media.selected').attr('id') ); playMedia('play');
  3124. break;
  3125. default:
  3126. switch(true) {
  3127. case selectRowID( media_class,key ) !== firstRowID( media_class ): // show and play next track if next track !== first track
  3128. case $body.hasClass('loop_audio'): // if loop audio, always show and play
  3129. showAudio( selectRowID( media_class,key ) );
  3130. playMedia('play');
  3131. break;
  3132. case selectRowID( media_class,key ) === firstRowID( media_class ): // if next track is first track, just show it
  3133. showAudio( selectRowID( media_class,key ) );
  3134. break;
  3135. }
  3136. break;
  3137. }
  3138. }
  3139. }
  3140. }
  3141. // NAVIGATION: Audio by prev/next audio buttons
  3142. $('.prev_next_track_btn').on('click',function() { let key = ( $(this).attr('id') === 'prev_track' ? 'ArrowLeft' : 'ArrowRight' ); playPrevNextTrack(key); });
  3143. function upDownArrowNavigation(e) {
  3144. switch(true) {
  3145. case ( /a|button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ) && window.top !== window.self ):
  3146. case ( /text_preview|html_preview/.test(document.activeElement.id) ):
  3147. return; // don't navigate if text editor previews are focussed
  3148. default:
  3149. document.activeElement.blur();
  3150. }
  3151. e.preventDefault();
  3152. let row_ID = selectRowID('.dir:visible,.file:visible',e.key);
  3153. let row = $(document.getElementById(row_ID));
  3154. let $selected = $('#tbody').find('.selected');
  3155. switch(true) {
  3156. case cmdKey(e) && e.key === 'ArrowUp': // Cmd/Ctrl + up arrow
  3157. switch(true) {
  3158. case window.self !== window.top:
  3159. $('#iframe_body #parent').find('a').click(); // go to iframe parent
  3160. break;
  3161. case window.self === window.top && $('body').hasClass('focus_content'): // fallback for go to iframe parent in case top is incorrectly focused
  3162. sendMessage('iframe','open_iframe_parent_dir');
  3163. break;
  3164. default:
  3165. showWarning( 'clickThis', $('#parent_dir_nav').attr('id') ); // go to parent (with warning)
  3166. break;
  3167. }
  3168. break;
  3169. case cmdKey(e) && e.key === 'ArrowDown': // Cmd/Ctrl + down arrow
  3170. switch(true) {
  3171. case window.self === window.top && $selected.hasClass('file') && !$selected.hasClass('link'): // do nothing for #top files
  3172. case window.self === window.top && $selected.hasClass('dir') && $selected.hasClass('app') && $settings.apps_as_dirs === false: // do nothing for apps if not viewing as dirs
  3173. break;
  3174. default: // else double-click directories and all iframe items to open them
  3175. $selected.find('a').trigger('dblclick');
  3176. break;
  3177. }
  3178. break;
  3179. case $body.hasClass('has_directory_source'): // if viewing directory source, arrows will reopen selected sidebar item
  3180. if ( $('#tbody .loaded').length > 0 ) { clickRow( $('#tbody .loaded').attr('id') ); } else { clickRow( row_ID ); }
  3181. scrollThis('tbody','selected');
  3182. break;
  3183. case cmdKey(e) && window.self !== window.top: // iframe && cmdKey
  3184. switch(true) {
  3185. case $('#iframe_body #dir_list').length === 0:
  3186. break;
  3187. }
  3188. break;
  3189. case window.top === window.self && $('body').hasClass('focus_content'):
  3190. focusContent();
  3191. sendMessage('iframe','iframe_arrow_navigation','',e.key);
  3192. $('#iframe_body a').click();
  3193. break;
  3194. default: // click prev/next item
  3195. selectThis(row_ID); // don't use $selected after this
  3196. switch(true) { // don't show content for media; only select row
  3197. case row.hasClass('audio'):
  3198. case row.hasClass('video') && $('.playing').hasClass('video'):
  3199. break;
  3200. default: showContent(row_ID);
  3201. }
  3202. if ( $('.selected').length ) { scrollThis('tbody','selected'); }
  3203. }
  3204. }
  3205. // NAVIGATION: Prev/Next Audio or Prev/Next File of same data-kind by arrow left/right
  3206. function leftRightArrowNavigation(class_name,e) {
  3207. switch(true) {
  3208. case ( /a|button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ) && window.top !== window.self ):
  3209. case ( /text_preview|html_preview/.test(document.activeElement.id) ):
  3210. return; // don't navigate if text editor previews are focussed
  3211. default:
  3212. document.activeElement.blur();
  3213. }
  3214. if ( cmdAltKey(e) && e.key === 'ArrowLeft' || cmdAltKey(e) && e.key === 'ArrowRight' ) { // don't start audio tracks when changing tabs
  3215. return;
  3216. } else {
  3217. e.preventDefault();
  3218. let $selected = $('#tbody').find('.selected');
  3219. switch(true) {
  3220. case e.altKey && e.shiftKey:
  3221. case e.altKey && !e.metaKey && !e.ctrlKey: { // Skip audio 30s/10s: Opt-Shift-Arrow/Opt-Arrow
  3222. let args = [e.key];
  3223. if ( e.shiftKey ) { args.push(30); } else { args.push(10); }
  3224. switch(true) {
  3225. case window.top !== window.self: sendMessage('top','mediaSkip','mediaSkip',args); break;
  3226. default: mediaSkip(e);
  3227. }
  3228. break;
  3229. }
  3230. case $('#content_pane').hasClass('has_audio') && window.top === window.self: playPrevNextTrack(e.key); break; // play prev/next audio track
  3231. case $body.hasClass('has_directory_source'): // if viewing directory source
  3232. if ( $('#tbody .loaded').length > 0 ) { clickRow( $('#tbody .loaded').attr('id') ); } else { clickRow( selectRowID('.dir,.file',e.key) ); }
  3233. scrollThis('tbody','selected');
  3234. break;
  3235. default:
  3236. switch(true) {
  3237. case $selected.length === 0: clickRow( selectRowID('.dir,.file',e.key) ); break; // click first/last row
  3238. case $selected.hasClass('audio'): clickRow( selectRowID('.audio',e.key) ); break; // click audio
  3239. case !$selected.hasClass('audio'): clickRow( selectRowID('.'+ $selected.attr('data-kind'),e.key) ); break;
  3240. }
  3241. if (class_name === 'video') { playMedia('play'); }
  3242. scrollThis('tbody','selected');
  3243. }
  3244. }
  3245. }
  3246. // NAVIGATE directory index items by arrow up/down or left/right keys, with/without grid @ indexNavigation()
  3247. function arrowNavigation(e) {
  3248. switch(true) {
  3249. case window.top !== window.self && document.activeElement.getAttribute('id') === 'iframe_body' && $('#iframe_body').find('#dir_list').length === 0: // allow arrows to work normally in iframes (but not iframe dirs)
  3250. case ( /img|textarea|embed/.test( document.activeElement.tagName.toLowerCase() ) ): // allow arrows to work normally in images and textareas
  3251. case document.activeElement.hasAttribute('contentEditable'): // allow arrows to work normally in contentEditable elements
  3252. break;
  3253. default:
  3254. e.preventDefault(); // otherwise prevent default arrow behavior
  3255. $('button').blur(); // Need this to able to select grid items after clicking close button (or other buttons).
  3256. case $('body').hasClass('has_menu'): // menu navigation
  3257. if ( window.top !== window.self ) { sendMessage('top','menu_navigation','menuNavigation',e.key); } else { menuNavigation(e.key); }
  3258. break;
  3259. case $content_pane.attr('data-content') === 'has_grid' && $('body').hasClass('focus_content') && !cmdKey(e): // navigate grid
  3260. case $content_pane.attr('data-content') === 'has_image' && $content_pane.hasClass('has_hidden_grid') && !cmdKey(e): // navigate hidden grid
  3261. case $content_pane.attr('data-content') === 'has_font' && $content_pane.hasClass('has_hidden_grid') && !cmdKey(e): // navigate hidden grid
  3262. gridNavigation('#content_grid',e.key);
  3263. if ( !$content_pane.hasClass('has_hidden_grid') ) { scrollThis('content_grid','selected'); }
  3264. scrollThis('tbody','selected');
  3265. break;
  3266. case $content_pane.attr('data-content') === 'has_font_file' && !cmdKey(e): // navigate font file glyphs
  3267. case $content_pane.attr('data-content') === 'has_glyph' && !cmdKey(e): // navigate font file glyphs
  3268. if ( $('body').hasClass('focus_content') ) {
  3269. gridNavigation('#glyphs_container',e.key);
  3270. scrollThis('content_container','selected');
  3271. } else {
  3272. showWarning('warning_close_font','warning_btn_cancel'); // show close font warning if sidebar focused
  3273. }
  3274. break;
  3275. case e.key === 'ArrowUp' || e.key === 'ArrowDown': // up/down arrow navigation
  3276. upDownArrowNavigation(e);
  3277. break;
  3278. case e.key === 'ArrowLeft' || e.key === 'ArrowRight': { // left/right arrow navigation
  3279. let class_name = ( $('.selected[data-kind]') === undefined ? $('#dir_list').find('tbody tr:visible').first().attr('data-kind') : $('.selected[data-kind]').attr('data-kind') );
  3280. leftRightArrowNavigation(class_name,e);
  3281. break;
  3282. }
  3283. }
  3284. }
  3285. // NAVIGATION: by typed string
  3286. var str = '';
  3287. function timeoutID() { return window.setTimeout( function() { str = ''; }, 1500 ); }
  3288. function alphaNav(e) { // Select Dir_List row by typed string; Todo: select next row by nearest letter
  3289. if ( document.activeElement.tagName.toLowerCase() === 'textarea' || document.activeElement.getAttribute('contentEditable') === true ) {
  3290. return;
  3291. } else {
  3292. let timer = timeoutID();
  3293. if ( typeof timer === 'number' ) {
  3294. window.clearTimeout( timer );
  3295. timer = 0; // id
  3296. }
  3297. timeoutID();
  3298. str += e.key;
  3299. str = str.toLowerCase();
  3300. if ( $('#dir_list').find('td.name[data-name^="'+ str +'"]').length ) {
  3301. $('#dir_list').find('td.name[data-name^="'+ str +'"]').first().find('a').click();
  3302. scrollThis('tbody','selected');
  3303. // } else {
  3304. // null; // replace this with some sort of fuzzy match function? TBD
  3305. }
  3306. }
  3307. }
  3308. //***** END NAVIGATION *****//
  3309.  
  3310. // SELECT ROW on click and set classes for $content_pane
  3311. function selectThis(row_ID) {
  3312. let row = $(getElById(row_ID));
  3313. switch(true) {
  3314. case row.hasClass('disabled'):
  3315. break;
  3316. case $content_pane.attr('data-content') === 'has_grid':
  3317. case $content_pane.hasClass('has_hidden_grid'): {// Select corresponding grid item
  3318. const $grid_selected = $content_grid.find('> div[data-id="'+ row_ID +'"]');
  3319. row.addClass('selected').siblings().removeClass('selected hovered');
  3320. $grid_selected.addClass('selected').siblings().removeClass('selected');
  3321. break;
  3322. }
  3323. default:
  3324. $body.removeClass('has_directory_source');
  3325. row.addClass('loaded').siblings().removeClass('loaded');
  3326. row.addClass('selected').siblings().removeClass('selected hovered'); // remove classes from sibling, but leave .audio with playing
  3327. row.siblings('.video').removeClass('playing');
  3328. }
  3329. }
  3330. // CLICK element by id
  3331. function clickThis(id) { let el = $(document.getElementById(id)); if ( el.find('a').length > 0 ) { el.find('a').click(); } else { el.click(); } }
  3332. // CLICK Row to show content
  3333. function clickRow(id) {
  3334. selectThis(id);
  3335. showContent(id);
  3336. }
  3337. // On click row, play/pause media or clickRow
  3338. $('#top #tbody').on('click','tr', function(e) {
  3339. e.preventDefault(); e.stopPropagation();
  3340. focusSidebar();
  3341. if ( $(this).hasClass('playing') ) {
  3342. playPauseMedia();
  3343. } else {
  3344. showWarning( 'clickRow', $(this).attr('id') );
  3345. }
  3346. });
  3347. // DOUBLE-CLICK Row to open directory
  3348. function doubleClickRow(args) { // row.dir only
  3349. let row = document.getElementById(args[0]);
  3350. if ( row.classList.contains('dir') && row.classList.contains('invisible') && row.classList.contains('ignored') ) { return } // don't attempt to open ignored invisible dirs (chiefly system dirs)
  3351. let id = args[0].slice(args[0].indexOf('-') + 1), href = args[1];
  3352. let query_str = decodeURIComponentSafe(window.location.search);
  3353. if ( query_str === '' ) { query_str = '?'; }
  3354. if ( query_str.indexOf('history') !== -1 ) {
  3355. query_str = query_str.replace(/&selected=\d+/,'').replace(/&history=/,'&history='+ id +'+');
  3356. } else {
  3357. query_str = query_str.replace(/&selected=\d+/,'') + '&history='+ id;
  3358. }
  3359. window.location = href + query_str;
  3360. }
  3361. $('#top').on('dblclick', '#tbody tr.dir,#tbody tr.link', function(e) {
  3362. e.preventDefault(); e.stopPropagation();
  3363. switch(true) {
  3364. case $(this).hasClass('link'):
  3365. if ( $protocol === 'file:' ) { sendMessage('iframe','open_link_file','',$(this).attr('data-ext')); } else { openLink($(this).attr('data-ext')); }
  3366. break;
  3367. default:
  3368. showWarning('doubleClickRow', [$(this).attr('id'), $(this).find('a').attr('href')] );
  3369. }
  3370. });
  3371. // click content header or audio to remove faded body class
  3372. $('#sidebar_title,#sidebar_buttons,#sidebar,#sorting .sorting').on('click',function(e) { focusSidebar(); });
  3373. // CLICK grid item
  3374. $content_grid.on('click','div', function(e) {
  3375. e.preventDefault();
  3376. let id = $(this).attr('data-id');
  3377. $(this).addClass('selected').siblings().removeClass('selected');
  3378. hideGrid();
  3379. getElById(id).click();
  3380. });
  3381. // HOVER Grid Item and highlight dir_list row
  3382. $content_grid.on('mouseenter','> div:not(.selected)',function() {
  3383. document.getElementById(this.getAttribute('data-id')).classList.add('hovered');
  3384. }).on('mouseleave','> div:not(.selected)',function() {
  3385. document.getElementById(this.getAttribute('data-id')).classList.remove('hovered');
  3386. });
  3387. // HOVER Dir_list_row: highlight corresponding grid item
  3388. $dir_list_body.on('mouseenter','> tr', function() {
  3389. if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_grid.find('> div[data-id="'+ $(this).attr('id') +'"]').addClass('hovered'); }
  3390. }).on('mouseleave','> tr', function() {
  3391. if ( $content_pane.attr('data-content') === 'has_grid' ) { $content_grid.find('> div[data-id="'+ $(this).attr('id') +'"]').removeClass('hovered'); }
  3392. });
  3393. // HOVER Footer Links: Fade dir_list
  3394. $('#footer_links').on('mouseenter',function() {
  3395. $('body:not(.has_menu), body:not(.has_menu_parents)').addClass('faded');
  3396. }).on('mouseleave',function() {
  3397. $('body:not(.has_menu), body:not(.has_menu_parents)').removeClass('faded');
  3398. });
  3399. // HOVER Stats Items: highlight dir_list items
  3400. $('#stats').on('mouseenter','#stats_details_container > div, #stats_summary_detailed_dirs, #stats_summary_detailed_files', function() {
  3401. let this_class = $(this).attr('class');
  3402. switch(true) {
  3403. case $(this).attr('id') === 'stats_summary_detailed_dirs': this_class = '.dir'; break;
  3404. case $(this).attr('id') === 'stats_summary_detailed_files': this_class = '.file'; break;
  3405. case /_/.test(this_class): this_class = '.'+ this_class.replace('_','.'); break;
  3406. default: this_class = '.'+ this_class +':not(.invisible):not(.ignored)';
  3407. }
  3408. $('#tbody').find(this_class).addClass('hovered');
  3409. }).on('mouseleave','div',function() {
  3410. $('#tbody tr').removeClass('hovered').removeAttr('style');
  3411. });
  3412. // END CLICK & HOVER EVENTS
  3413.  
  3414. //***** AUTOLOAD CONTENT: index files or files from the file shortcut list *****//
  3415. function autoLoadFile() {
  3416. let selected_ID = ( getQuery('selected').length > 0 ? 'rowid-'+ getQuery('selected') : undefined);
  3417. switch(true) {
  3418. case $('body').hasClass('has_audio') && getQuery('autoload_media') === 'true': // load audio; must be above next case
  3419. clickRow( $('.audio').first().attr('id') ); // load audio
  3420. if ( $('body').hasClass('has_images') ) { autoLoadCoverArt(); } else if ( selected_ID !== undefined ) { clickRow( selected_ID ); } // autoload cover art or select dir
  3421. break;
  3422. case document.getElementById(selected_ID) === null: // in case item with selected id is not found (e.g., item has been deleted, error in updating query_str, etc.)
  3423. case window.self !== window.top: // do nothing for iframes
  3424. break;
  3425. case $('body').hasClass('has_video') && getQuery('autoload_media') === 'true': // load video (if no audio)
  3426. clickRow( firstRowID('.video') );
  3427. break;
  3428. case getQuery('file') !== undefined && getQuery('file').length > 0: // load files (from bookmark or url)
  3429. clickRow( $dir_list.find('a[href*="'+ getQuery('file') +'"]').closest('tr').attr('id') );
  3430. removeQuery('file');
  3431. break;
  3432. case getQuery('autoload_index_files') === 'true' && $dir_list.find( 'a[href*="/index."]').length > 0: // else load index file
  3433. clickRow($('a[href*="/index."]').closest('tr').attr('id'));
  3434. break;
  3435. case selected_ID !== undefined:
  3436. showContent( selected_ID );
  3437. break;
  3438. }
  3439. if ( $('#tbody').find('.selected').length > 0 ) { scrollThis('tbody','selected'); }
  3440. }
  3441. // get cover art names
  3442. function getImageNames() {
  3443. let images = $dir_list_body.find('.image'), image_names = [], image_id, image_name;
  3444. for ( let i = 0; i < images.length; i++ ) {
  3445. image_id = images.eq(i).attr('id');
  3446. image_name = images.eq(i).find('.name').attr('data-name');
  3447. image_name = image_name.slice(0,image_name.lastIndexOf('.') ); // remove extension
  3448. image_names.push({'id':image_id,'name':image_name}); // add id and name
  3449. }
  3450. return image_names;
  3451. }
  3452. // get cover art id
  3453. function getCoverArtID() {
  3454. const cover_names = ['cover','front','album','jacket','sleeve','cd','disc','insert','liner','notes'];
  3455. const image_names = getImageNames();
  3456. let id, exact_match, match, cover_name;
  3457. for ( cover_name of cover_names ) { // test available image names against cover names
  3458. exact_match = image_names.filter( el => el.name === cover_name ); // check for exact match (w/o extension)
  3459. match = image_names.filter( el => el.name.indexOf(cover_name) > -1 ); // check for partial match
  3460. if ( exact_match.length > 0 ) { return id = exact_match[0].id; }
  3461. else if ( match.length > 0 ) { return id = match[0].id; }
  3462. }
  3463. if (id === undefined ) { return id = image_names[0].id; } // if no matches, return first image id
  3464. }
  3465. // Autoload Cover Art
  3466. function autoLoadCoverArt() { // autoload cover art for audio if dir contains audio and images, and audio is loaded and non-image content is not loaded
  3467. let cover_ID = getCoverArtID();
  3468. if ( cover_ID !== undefined ) {
  3469. let row = getElById(cover_ID);
  3470. row.addClass('loaded');
  3471. $content_pane.attr('data-content','has_image').find('#content_image').addClass('has_content').attr('src',row.find('a').attr('href'));
  3472. setContentTitle('image',decodeURIComponentSafe(row.find('.name a').attr('href')));
  3473. }
  3474. }
  3475. //***** KEYBOARD EVENTS *****//
  3476. function metaKey(e) { return ( navigator.platform.match('Mac') ? e.metaKey && !e.ctrlKey : e.ctrlKey); }
  3477. function cmdKey(e) { return ( metaKey(e) && !e.altKey && !e.shiftKey ); }
  3478. function cmdAltKey(e) { return ( metaKey(e) && e.altKey && !e.shiftKey ); }
  3479. // function cmdAltShiftKey(e) { return ( metaKey(e) && e.altKey && e.shiftKey ); }
  3480. function cmdShiftKey(e) { return ( metaKey(e) && !e.altKey && e.shiftKey ); }
  3481.  
  3482. $('#top, #iframe_body').on('keydown', function(e) {
  3483. const $selected = ( $('#content_pane').attr('data-content') === 'has_font_file' ? $('.glyph_container.selected .glyph') : $('#tbody').find('.selected') );
  3484. switch(true) { // Disable all keydown events except return and tab when help or warning is shown, or when textarea is focused
  3485. case document.activeElement.hasAttribute('contentEditable') && !/Escape|Tab/.test(e.key) && !( cmdKey(e) && e.key === 'w'):
  3486. case ( /button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ) && !/escape|tab|shiftkey|metakey|altkey/.test(e.key.toLowerCase()) ):
  3487. case ($('body').hasClass('has_warning') || $('body').hasClass('has_help') ) && !( cmdKey(e) || metaKey(e) || (/shiftkey|tab|enter|escape/.test(e.key.toLowerCase()) ) ):
  3488. return;
  3489. }
  3490. // MAIN KEYDOWN EVENTS
  3491. switch ( e.key ) {
  3492. case 'shiftKey':
  3493. switch(true) {
  3494. case $('body').hasClass('has_warning') || $('body').hasClass('has_help'):
  3495. if (e.key !== 'Enter' && e.key !== 'Tab') { e.preventDefault(); return false; }
  3496. break;
  3497. }
  3498. break;
  3499. case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight':
  3500. showWarning( 'arrowNavigation',e ); // arrow navigation
  3501. break;
  3502. case ' ': // space
  3503. if ( $('#content_pane').hasClass('has_audio') || $('#content_pane').attr('data-content') === 'has_video' ) { // Play/pause media (space bar)
  3504. e.preventDefault(); e.stopPropagation(); playPauseMedia();
  3505. } else if ( window.top !== window.self && $('tr.audio.playing').length === 1 ) {
  3506. e.preventDefault(); e.stopPropagation(); sendMessage('top','playPauseMedia');
  3507. } else {
  3508. alphaNav(e);
  3509. }
  3510. break;
  3511. case 'Enter': // Open directories (or ignored)
  3512. switch(true) {
  3513. case $('body').hasClass('has_warning') || $('body').hasClass('has_help'):
  3514. e.preventDefault();
  3515. $('button.focus, button:focus').click(); // click focused warning button in #top or #iframe
  3516. break;
  3517. case window.self !== window.top: // if iframe...
  3518. switch(true) {
  3519. case $('body').hasClass('has_menu'):
  3520. sendMessage('top','clickMenu'); // close main menu
  3521. break;
  3522. case $('tr.selected').hasClass('dir') && cmdKey(e):
  3523. case $('tr.selected').hasClass('file') && cmdKey(e): // open iframe dir or file
  3524. case $('tr.selected').hasClass('link') && cmdKey(e): // webloc or url file
  3525. $('tr.selected').find('a').trigger('dblclick');
  3526. break;
  3527. case !$('.audio.selected').hasClass('playing'):
  3528. $('.audio.selected').find('a').trigger('dblclick');
  3529. break;
  3530. case $('.audio.playing').length === 1 && !$('.selected').hasClass('playing'): // play/pause media
  3531. e.preventDefault(); e.stopPropagation(); playPauseMedia();
  3532. break;
  3533. }
  3534. break;
  3535. case $('body').hasClass('has_menu'):
  3536. e.preventDefault(); clickMenu(); sendMessage('iframe','close_menu'); // click selected menu item
  3537. break;
  3538. case $selected.hasClass('app') && $settings.apps_as_dirs === false: // don't open app folders
  3539. break;
  3540. default:
  3541. switch(true) {
  3542. case $selected.hasClass('.disabled'):
  3543. case $content_pane.attr('data-content') === 'has_text_editor':
  3544. break;
  3545. case $selected.hasClass('media') && !$selected.hasClass('playing'): // load selected media file
  3546. $('.media.selected a').click();
  3547. break;
  3548. case $selected.hasClass('media'): // else play/pause playing media
  3549. e.preventDefault(); e.stopPropagation(); playPauseMedia();
  3550. break;
  3551. case $selected.hasClass('link'):
  3552. case $selected.hasClass('dir'): // else open dir
  3553. case $('tr.selected').hasClass('link') && cmdKey(e): // webloc or url file
  3554. $selected.find('a').trigger('dblclick');
  3555. break;
  3556. default:
  3557. $selected.click();
  3558. }
  3559. }
  3560. break;
  3561. // Alphabetical navigation
  3562. case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'X': case 'Y': case 'Z':
  3563. 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':
  3564. 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 'π':
  3565. 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 '≠':
  3566. case '⁄': case '€': case '‹': case '›': case 'fl': case '‡': case '°': case '·': case '‚': case '±': case '¯': case '˘': case '¿':
  3567. case 'ı': case '': case '´': case '˝': case 'ˆ': case '': case '˜': case '‰': case 'ˇ': case '¨': case '◊': case '„': case '˛': case '¸':
  3568. // 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 'ϖ':
  3569. // 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 'ϒ':
  3570. if ( !e.metaKey && !e.ctrlKey && !e.altKey ) { alphaNav(e); }
  3571. break;
  3572. case 'd': // Cmd/Ctrl + D: Toggle Details
  3573. if ( cmdKey(e) && !$body.hasClass('has_warning') ) { e.preventDefault(); $('#show_details').click(); } else { alphaNav(e); }
  3574. break;
  3575. case 'e': // Cmd/Ctrl + E: Toggle Main Menu or Text Editor
  3576. switch(true) {
  3577. case $body.hasClass('has_warning'): break;
  3578. case cmdShiftKey(e): // toggle text editor
  3579. e.preventDefault();
  3580. if ( window.self !== window.top ) { sendMessage('top','toggle_text_editor'); } else { $('#text_editor_row').find('a').click(); }
  3581. $('body').addClass('faded');
  3582. break;
  3583. case cmdKey(e): // toggle main menu
  3584. e.preventDefault();
  3585. if ( window.self !== window.top ) { sendMessage('top','toggle_menu'); } else { $('#menu_container').click(); }
  3586. break;
  3587. default:
  3588. alphaNav(e);
  3589. }
  3590. break;
  3591. case 'g': // Cmd/Ctrl + G: Show image Grid
  3592. if ( cmdKey(e) && ( $('#top').hasClass('has_images') || $('#top').hasClass('has_fonts') ) ) { e.preventDefault(); $('#grid_btn').click(); } else { alphaNav(e); }
  3593. break;
  3594. case 'i': // Cmd/Ctrl + I: Toggle Invisibles
  3595. switch(true) {
  3596. case cmdKey(e):
  3597. e.preventDefault();
  3598. if ( window.self !== window.top ) { sendMessage('top','toggle_invisibles'); } else { $('#show_invisibles_container').find('input').click(); }
  3599. break;
  3600. default: alphaNav(e);
  3601. }
  3602. break;
  3603. case 'o': // Cmd/Ctrl + Shift + O: Open selected item in new window
  3604. if ( cmdShiftKey(e) ) { window.open( $('#tbody').find('.selected').find('a').attr('href') ); } else { alphaNav(e); }
  3605. break;
  3606. case 'r': // Cmd/Ctrl + Shift + R: Refresh
  3607. switch(true) {
  3608. case cmdKey(e) && window.top !== window.self:
  3609. e.preventDefault(); sendMessage('top','reload'); // send reload message to top
  3610. break;
  3611. case cmdKey(e):
  3612. e.preventDefault(); $('#reload_btn').click(); // click reload/reset button
  3613. break;
  3614. default:
  3615. alphaNav(e);
  3616. }
  3617. break;
  3618. case 'w': // Cmd/Crtl + W: if content pane has content, click close button; otherwise default behavior (close tab/window); Firefox does not allow scripts to override this behavior
  3619. switch(true) {
  3620. case cmdKey(e):
  3621. switch(true) {
  3622. case window.top !== window.self:
  3623. e.preventDefault();
  3624. sendMessage('top','close'); // send close message to top
  3625. break;
  3626. case $content_pane.hasClass('has_audio'):
  3627. case $content_pane.attr('data-content') !== undefined:
  3628. e.preventDefault();
  3629. $('#close_btn').click(); // click close button
  3630. break;
  3631. default:
  3632. return true; // else close window (or normal behavior)
  3633. }
  3634. break;
  3635. default:
  3636. alphaNav(e);
  3637. }
  3638. break;
  3639. case '=': // equals sign
  3640. switch(true) {
  3641. case cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test($content_pane.attr('data-content')):
  3642. e.preventDefault(); $('#increase').click();
  3643. break;
  3644. case cmdKey: // allow normal Cmd + behavior
  3645. return;
  3646. default:
  3647. alphaNav(e);
  3648. break;
  3649. }
  3650. break;
  3651. case '-': // hyphen
  3652. switch(true) {
  3653. case cmdKey(e) && /has_grid|has_image|has_font|has_glyph|has_font_file/.test($content_pane.attr('data-content')):
  3654. e.preventDefault(); $('#decrease').click();
  3655. break;
  3656. case cmdKey: // allow normal Cmd - behavior
  3657. return;
  3658. default:
  3659. alphaNav(e);
  3660. break;
  3661. }
  3662. break;
  3663. case '\\': // \ backslash
  3664. switch(true) {
  3665. case cmdShiftKey(e): // Cmd Shift + \ : toggle split
  3666. switch(true) {
  3667. case window.top === window.self && $content_pane.attr('data-content') === 'has_iframe':
  3668. sendMessage('iframe','toggle_split'); // send toggle split message to top
  3669. break;
  3670. case $('#toggle_split').height() > 0:
  3671. $('#toggle_split').click(); // click toggle split
  3672. break;
  3673. }
  3674. break;
  3675. case cmdKey(e): // Cmd + \ : toggle sidebar
  3676. if ( window.top !== window.self ) { sendMessage('top','toggle_sidebar'); } else { $('#toggle_sidebar').click(); }
  3677. break;
  3678. }
  3679. break;
  3680. case 'Tab':
  3681. e.preventDefault();
  3682. switch(true) {
  3683. case $('body').hasClass('has_warning'):
  3684. tabWarningButtons(e); break; // focus warning buttons
  3685. case document.activeElement.id === 'content_image':
  3686. focusSidebar(); break; // focus sidebar from image
  3687. case $content_pane.attr('data-content') === 'has_text_editor':
  3688. focusTextEditorPanes(); break; // focus text editor
  3689. case (/has_markdown|has_text|has_code|has_htm/.test($content_pane.attr('data-content')) && window.top === window.self ):
  3690. $('body').addClass('focus_content'); sendMessage('iframe','focus_iframe'); break; // focus iframe text editor or htm files from top
  3691. case ( /text_preview|html_preview|text_source/.test(document.activeElement.id) ):
  3692. toggleTextEditorPanes(); break; // toggle focused text editor panes
  3693. case $(document.activeElement).closest('#text_preview').length === 1:
  3694. focusFocusableEls(e,'#text_preview'); break; // focus focusable elements in text preview
  3695. case $(document.activeElement).closest('#font_specimen').length === 1:
  3696. focusFocusableEls(e,'#font_specimen'); break; // focus focusable elements in font specimen
  3697. case document.activeElement.hasAttribute('contenteditable'):
  3698. case window.top !== window.self: // If iframe is focused, focus sidebar, dim selected iframe dir list item
  3699. e.preventDefault();
  3700. switch(true) {
  3701. case $('#iframe_dir_list_wrapper').length === 1:
  3702. case !/a|button|input|select|textarea/.test(document.activeElement.tagName.toLowerCase() ): // allow form elements to be focused
  3703. $('#iframe_body,#dir_list tr.selected').addClass('is_blurred'); // dim iframe dir_list
  3704. sendMessage('top','tab'); // focus sidebar
  3705. break;
  3706. case $('a,button,input,select,textarea').length > 0: // allow tabbing to form elements and textareas (e.g., in previewed html pages)
  3707. if ( $('body').hasClass('has_text_editor_UI') ) { focusFocusableEls(e,'#text_container'); } else { focusFocusableEls(e); }
  3708. break;
  3709. }
  3710. break;
  3711. case window.top === window.self: // Either focus sidebar or refocus content pane (after clicking menu, for example)
  3712. switch(true) {
  3713. case $('body').hasClass('focus_content') && $('body').hasClass('has_menu'): case !$('body').hasClass('focus_content'): focusContent('',e); break;
  3714. case $('body').hasClass('focus_content'): case $('body').hasClass('has_menu'): focusSidebar(); break;
  3715. }
  3716. break;
  3717. }
  3718. break;
  3719. case 'Escape': // remove text selections, close warnings or help, close menus (and refocus content), or focus sidebar.
  3720. window.getSelection().removeAllRanges();
  3721. $('#content_playlist, #content_audio_playlist').removeClass('has_content');
  3722. $('#warning_btn_cancel,#close_help').click();
  3723. if ( window.top !== window.self ) { $('#iframe_body').addClass('is_blurred'); sendMessage('top','escape'); } else { focusSidebar(); }
  3724. break;
  3725. case '.':
  3726. switch(true) {
  3727. case cmdKey(e):
  3728. if ( $('body').hasClass('has_warning') ) {
  3729. e.preventDefault();
  3730. $('#warning_btn_cancel,#close_help').click(); // click cancel buttons
  3731. }
  3732. break;
  3733. default:
  3734. break;
  3735. }
  3736. } // end switch
  3737. });
  3738. // ***** END KEYBOARD EVENTS ***** //
  3739.  
  3740. // ***** GRID SETUP ***** //
  3741. // Create Font Grid Items
  3742. function fontGridItems() {
  3743. let $font_grid_items_arr = [], $font_files = $dir_list_body.find('.font'), new_grid_item, font_grid_styles = $font_grid_styles.sheet;
  3744. for ( let i = $font_files.length; i--; ) {
  3745. new_grid_item = setContentFontSource( $font_files.eq(i).attr('id'), true, font_grid_styles );
  3746. $font_grid_items_arr.unshift( new_grid_item );
  3747. }
  3748. $font_grid_items_arr[$font_grid_items_arr.length - 1].addClass('border_bottom_x'); // add bottom border to last
  3749. return $font_grid_items_arr;
  3750. }
  3751. // Create Image Grid Items
  3752. function imageGridItems() {
  3753. let $image_grid_items_arr = [], $image_files = $dir_list_body.find('.image:not(.ignored)'), classes = 'image_grid_item border_right_x border_bottom_x';
  3754. for ( let i = $image_files.length; i--; ) {
  3755. const id = $image_files.eq(i).attr('id');
  3756. const this_link = $image_files.eq(i).find('a').attr('href');
  3757. const exts = $item_kind.image.filter( ext => $.inArray(ext, $row_settings.ignored) == -1 ); // decide which image files can be displayed
  3758. const title_name = this_link.slice(this_link.lastIndexOf('/') + 1);
  3759. if ( $.inArray( $image_files.eq(i).attr('data-ext'), exts ) > -1 ) { // if this row file ext is in the image extension array
  3760. let item = '<div class="'+ classes +' background_color_EE_22" data-ID="'+ id +'" data-index="'+ i +'"><a href="'+this_link+'"><img src="'+this_link+'" title="'+title_name+'" loading="lazy" /></a></div>';
  3761. $image_grid_items_arr.unshift( item );
  3762. }
  3763. }
  3764. return $image_grid_items_arr;
  3765. }
  3766. // Make Grids
  3767. function makeGrids(id) {
  3768. closeGrid(); // remove previous grid items
  3769. $content_pane.removeClass('has_hidden_grid has_image_grid has_font_grid'); // reset content_pane grid classes
  3770. switch(true) {
  3771. case id === 'show_font_grid' || !$body.hasClass('has_images'): // only show font grid
  3772. $content_pane.addClass('has_font_grid'); $content_grid.append( fontGridItems() );
  3773. break;
  3774. case id === 'show_image_grid' || !$body.hasClass('has_fonts'): // only show image grid
  3775. $content_pane.addClass('has_image_grid'); $content_grid.append( imageGridItems() );
  3776. break;
  3777. default: // show grid of both images and fonts
  3778. $content_grid.append( imageGridItems(), fontGridItems() );
  3779. }
  3780. }
  3781. // Click grid button: make grid and show grid
  3782. $('#sidebar_header').on('click', '#grid_btn, #show_font_grid, #show_image_grid', function(e) {
  3783. e.stopPropagation(); showContent($(this).attr('id'));
  3784. });
  3785.  
  3786. // ***** SCALE PREVIEWED IMAGES & FONTS ***** //
  3787. // Scale Fonts
  3788. function scaleFonts(e, incr, id) {
  3789. const font_size = parseInt(getComputedStyle(document.body).fontSize); // pts/em
  3790. const getFontSize = function(el) { return parseFloat(el.css('font-size')); };
  3791. if ( id === 'decrease' ) { incr = 1/incr; }
  3792. if ( $content_pane.attr('data-content') === 'has_grid' ) {
  3793. $content_grid.css({'font-size':( getFontSize($content_grid)/font_size * incr ) +'em'});
  3794. return;
  3795. }
  3796. if ( $content_pane.attr('data-content') === 'has_font' ) {
  3797. $content_font.css({'font-size':( getFontSize($content_font)/font_size * incr ) +'em'});
  3798. return;
  3799. }
  3800. scrollThis('content_container','content_font');
  3801. }
  3802. // Scale Glyphs
  3803. function scaleGlyphs(e, incr, id) {
  3804. if ( id === 'decrease' ) { incr = 1/incr; }
  3805. let scale = ( $('#glyph_viewer').attr('data-scale') === undefined ? incr : incr * $('#glyph_viewer').attr('data-scale') );
  3806. if ( scale >= 1 ) {
  3807. $('#glyph_viewer').css({'width': scale * 100 +'%','height': scale * 100 +'%'});
  3808. document.getElementById('content_font').scrollLeft = -( Math.round( $('#font_viewer').outerWidth(true) - $('#glyph_viewer').width() ) / 2 );
  3809. document.getElementById('content_font').scrollTop = -( Math.round( $('#font_viewer').outerHeight(true) - $('#glyph_viewer').height() ) / 2 );
  3810. }
  3811. $('#glyph_viewer').css({'background-size': scale * 100 +'%'}).attr('data-scale',scale.toFixed(2));
  3812. }
  3813. // Scale Image Grid items
  3814. function scaleGridImages(incr,id) {
  3815. if ( id === 'decrease' ) { incr = 1/incr; }
  3816. const $image_grid_item = $('.image_grid_item img');
  3817. let image_grid_item_width = Number.parseFloat( $image_grid_item.width(),10) * incr;
  3818. let image_grid_item_height = Number.parseFloat( $image_grid_item.height(),10) * incr;
  3819. let image_grid_item_max_width = Number.parseFloat( $image_grid_item.css('maxWidth'),10) * incr;
  3820. let image_grid_item_max_height = Number.parseFloat( $image_grid_item.css('maxHeight'),10) * incr;
  3821. // prevent reducing grid image size on first scale click:
  3822. if ( image_grid_item_width < image_grid_item_max_width ) { image_grid_item_width = image_grid_item_max_width; }
  3823. if ( image_grid_item_height < image_grid_item_max_height ) { image_grid_item_height = image_grid_item_max_height; }
  3824. // set grid properties
  3825. $content_grid.css({'grid-template-columns':'repeat(auto-fill, minmax('+ (image_grid_item_width +16) +'px, auto ) )'});
  3826. $content_grid.find('img').css({'max-width':( image_grid_item_width ) +'px', 'max-height':( image_grid_item_height ) +'px'});
  3827. return;
  3828. }
  3829. // Zoom Images on click
  3830. function scaleImages(e,incr,id) {
  3831. const $content_container = ( $('#iframe_body > img').length === 1 ? $('#iframe_body') : $('#content_container') );
  3832. const $this_image = ( $('#iframe_body > img').length === 1 ? $('#iframe_body > img') : $content_image );
  3833. const this_link = $this_image.attr('src');
  3834. let CC_width = function() { return Math.round($content_container.width()); };
  3835. let CC_height = function() { return Math.round($content_container.height()); };
  3836. const this_width = Math.round($this_image.width());
  3837. const this_height = Math.round($this_image.height());
  3838. const iframe_delta = ( $('#iframe_body > img').length === 1 ? Number.parseInt($('#iframe_body').css('padding')) : 0 );
  3839. // scale grid images
  3840. if ( $content_pane.attr('data-content') === 'has_grid' ) {
  3841. scaleGridImages(incr,id);
  3842. } else { // scale single images
  3843. getImageDimensions( this_link, function( width, height ) {
  3844. if ( incr !== undefined && id !== undefined ) { // scale images by increment
  3845. $content_pane.addClass('has_scaled_image').removeClass('has_zoom_image'); // remove zoom classes in case window resized after zoom
  3846. if ( id === 'increase' ) {// && this_width < width && this_height < height) {
  3847. $this_image.css({'width':this_width * incr, 'height': this_height * incr});
  3848. $this_image.css({'width':this_width * incr, 'height': 'auto', 'max-width':'none', 'max-height':'none' });
  3849. }
  3850. if ( id === 'decrease' && ( this_width >= 1 && this_height >= 1 ) ) {
  3851. $this_image.css({'width':this_width / incr, 'height': 'auto', 'max-width':'none', 'max-height':'none' });
  3852. }
  3853. // keep images centered when scaling
  3854. if ( Math.round($this_image.width()) >= CC_width() ) {
  3855. $content_image_container.scrollLeft( ( Math.round( ( $this_image.outerWidth(true) ) - CC_width() ) )/2 ) ;
  3856. }
  3857. if ( Math.round($this_image.height()) <= CC_height() ) {
  3858. $content_image_container.scrollTop( ( CC_height() - Math.round( $this_image.height() ) )/2 );
  3859. } else {
  3860. $content_image_container.scrollTop( ( Math.round($this_image.outerHeight(true)) - CC_height())/2 ) ;
  3861. }
  3862. } else { // else zoom single image on click
  3863. $this_image.removeAttr('style');
  3864. if ( width <= CC_width() && height <= CC_height() ) { // but don't zoom small images:
  3865. $content_pane.removeClass('has_zoom_image has_scaled_image');
  3866. return;
  3867. } // otherwise, zoom image:
  3868. const $CC_offset = $content_container.offset();
  3869. const $img_offset = $this_image.offset();
  3870. // x,y coordinates of zoom click as percentage of image width/height
  3871. const percentX = (e.pageX - $img_offset.left)/this_width;
  3872. const percentY = (e.pageY - $img_offset.top)/this_height;
  3873. // calculate scroll by pixel coordinates of full-size image - click coordinates and content_container offsets
  3874. const scrollX = (width * percentX) - e.pageX + $CC_offset.left - (iframe_delta * width / this_width);
  3875. const scrollY = (height * percentY) - e.pageY + $CC_offset.top - (iframe_delta * height / this_height);
  3876.  
  3877. $content_pane.removeClass('has_scaled_image').toggleClass('has_zoom_image' );
  3878. $content_image_container.scrollLeft( scrollX );
  3879. $content_image_container.scrollTop( scrollY );
  3880. }
  3881. });
  3882. setImageDimensions();
  3883. }
  3884. }
  3885. // Zoom single image on click
  3886. $content_image_container.on('click','img', function(e) { scaleImages(e); focusContent('content_image_container'); });
  3887. // Scale Fonts and Images
  3888. function scalePreviewItems(e,id) { // combine scaling into one function
  3889. if ( $content_pane.attr('data-content') === 'has_glyph' ) { scaleGlyphs(e, 1.125, id); return; }
  3890. scaleImages(e, 1.125, id );
  3891. scaleFonts(e, 1.125, id );
  3892. }
  3893. // Scale Buttons
  3894. $('#scale').on('click','span', function(e) {
  3895. e.preventDefault(); e.stopPropagation();
  3896. scalePreviewItems(e, $(this).attr('id') );
  3897. $('#reload_btn').addClass('reset');
  3898. if ( $('body').hasClass('focus_content') ) { focusContent(); }
  3899. });
  3900. // ***** END SCALE PREVIEW ITEMS ***** //
  3901.  
  3902. // ***** AUDIO CONTENT ***** //
  3903. // Update Playlist
  3904. function updatePlaylist() {
  3905. let playlist = [];
  3906. $dir_list_body.find('.audio').not('.unchecked').not('.disabled').each(function() { playlist.push( $(this).attr('id') ); });
  3907. return playlist;
  3908. }
  3909. // Randomize Shuffle List
  3910. function shuffleArray(array) {
  3911. for ( let i = array.length - 1; i > 0; i-- ) {
  3912. const j = Math.floor(Math.random() * (i + 1));
  3913. [array[i], array[j]] = [array[j], array[i]];
  3914. }
  3915. return array;
  3916. }
  3917. // Attach Shuffle List data to $audio_player
  3918. function updateShuffleList(id) {
  3919. let shuffle_list;
  3920. switch(true) {
  3921. case !$body.hasClass('shuffle_audio'):
  3922. break;
  3923. case id !== undefined: // don't include .playing and .unchecked track in shufflelist
  3924. shuffle_list = $audio_player.data('shufflelist');
  3925. switch(true) {
  3926. case $(document.getElementById(id)).hasClass('unchecked') || $(document.getElementById(id)).hasClass('playing'):
  3927. shuffle_list.splice(shuffle_list.indexOf(id), 1); $audio_player.data('shufflelist',shuffle_list); break;
  3928. default:
  3929. shuffle_list.push(id); shuffle_list = shuffleArray( shuffle_list );
  3930. }
  3931. break;
  3932. default:
  3933. shuffle_list = shuffleArray( updatePlaylist() );
  3934. $audio_player.data('shufflelist',shuffle_list);
  3935. }
  3936. }
  3937. // Check/Uncheck single Audio/Video Files and update shufflelist
  3938. function toggleChecked(el) { $(el).blur(); $(el).closest('tr').toggleClass('unchecked'); updateShuffleList($(el).closest('tr').attr('id')); }
  3939. $('#tbody').on('click','.media input', function(e) { e.stopPropagation(); toggleChecked($(this)); });
  3940. // Check/Uncheck all Audio/Video Files and update shufflelist
  3941. function toggleAllChecked() { $dir_list_body.find('> tr').find('input').trigger('click'); updateShuffleList(); }
  3942. // Is Playing; returns true if all conditions are true
  3943. function isPlaying(el) { return (el !== undefined && el.get(0).currentTime > 0 && !el.get(0).paused && !el.get(0).ended); }
  3944. // Play Media
  3945. function playMedia(task) {
  3946. if ( $content_pane.hasClass('has_audio') ) { $audio_player.trigger(task); } else { $content_video.trigger(task); }
  3947. }
  3948. // Skip media tracks +/-10/30 seconds
  3949. function mediaSkip(e,args) {
  3950. let factor, skip;
  3951. switch(true) {
  3952. case e !== undefined: // from top
  3953. factor = ( e.key === 'ArrowLeft' ? -1 : 1 ); // forward or backward?
  3954. skip = ( e.altKey && e.shiftKey ? 30 : e.altKey ? 10 : null ); // 30s or 10s?
  3955. break;
  3956. case args !== undefined: // from iframe
  3957. factor = ( args[0] === 'ArrowLeft' ? -1 : 1 ); // forward or backward?
  3958. skip = args[1]; // 30s or 10s?
  3959. break;
  3960. }
  3961. const $player = ( $('#content_pane').hasClass('has_audio') ? $audio_player : $content_video ); // audio or video?
  3962. const time = $player.prop('currentTime'); // current time
  3963. $player.prop('currentTime', time + factor*(skip)); // set time
  3964. }
  3965.  
  3966. // Play/Pause Audio/Video
  3967. function playPauseMedia() {
  3968. let $player = $('#audio');
  3969. if ( $content_pane.attr('data-content') === 'has_video' ) { $player = $('#content_video'); }
  3970. if ( isPlaying( $player ) ) { $player.trigger('pause'); } else { $player.trigger('play'); }
  3971. }
  3972. // Toggle Audio Playback Options (shuffle, loop)
  3973. function audioPlaybackOptions(id) {
  3974. if ( id === 'shuffle' ) {
  3975. $body.toggleClass('shuffle_audio');
  3976. updateShuffleList();
  3977. if ( $body.hasClass('shuffle_audio') && $('.playing').length === 0 ) { playPrevNextTrack('ArrowRight'); } else { /* do nothing: i.e., allow current track to continue playing */ }
  3978. } else {
  3979. $body.toggleClass('loop_audio');
  3980. document.getElementById('audio').toggleAttribute('loop');
  3981. }
  3982. }
  3983. // click loop or shuffle audio options
  3984. $('#audio_options').on('click','input', function() { audioPlaybackOptions( $(this).attr('id') ); });
  3985. // Initialize Media: play next track on ended and scroll to playing item
  3986. function initMedia() { $('#audio, #content_video').on('ended', function() {
  3987. $('#next_track').click();
  3988. scrollThis('tbody','playing'); });
  3989. }
  3990. // ***** END AUDIO PLAYBACK ***** //
  3991.  
  3992. // ***** IFRAME SETUP ***** //
  3993. function setUpIframeUI() { // set up iframe directory UI or iframe text editor
  3994. let link = decodeURIComponentSafe(window.location.href);
  3995. $('body').attr('id','iframe_body'); // add iframe body id
  3996. $('video').css({'width':'100%'});
  3997. const text_files = $item_kind.markdown.concat($item_kind.text, $item_kind.code); // define which files are editable
  3998. switch(true) {
  3999. case window.location.pathname.endsWith('/'): // set up iframe directory
  4000. setUpIframeDirUI(link);
  4001. break;
  4002. case text_files.includes( window.location.pathname.slice( window.location.pathname.lastIndexOf('.') + 1 ) ): // else set up iframe text editor
  4003. setUpTextEditorUI();
  4004. break;
  4005. default: break; // all other iframe content (e.g., html files)
  4006. }
  4007. sendMessage('top','iframe_loaded','',[link,'',window.location.pathname,window.location.protocol +'//']);
  4008. }
  4009. // IFRAME DIRECTORY Prep
  4010. function setUpIframeDirUI(link) { // set up iframe dir_list UI
  4011. let parent_link = link.split('/').slice(0,-2).join('/') +'/';//+ window.location.search;
  4012. let query_str = new URLSearchParams(window.location.search.toString().slice(1));
  4013. // let sort_direction = ( getQuery('sort_direction') === 'up' ? -1 : 1 ); // get sort direction
  4014. let $iframe_body = $('#iframe_body'), $iframe_head = $('head');
  4015. let body_classes = []; // make array of classes to add all at once
  4016. switch(true) {
  4017. case query_str.get('view_directory_source') === 'true':
  4018. case query_str.get('is_error') === 'true':
  4019. break; // show raw directory index if error or viewing directory source
  4020. default: // else set up iframe directory
  4021. for ( let key of query_str.keys() ) {
  4022. switch(true) {
  4023. case query_str.get(key) === 'true': // add body classes for boolean params
  4024. body_classes.push(key);
  4025. break;
  4026. case query_str.get(key) !== 'false': // = non-boolean params (theme, sort, sort_direction)
  4027. body_classes.push(key+'_'+getQuery(key));
  4028. break;
  4029. }
  4030. }
  4031. if ( getBrowser() === 'is_gecko' ) { body_classes.push('is_gecko'); } // add body class for firefos
  4032. // $iframe_body.data('sort_direction',sort_direction); // initial directory sort
  4033. $iframe_head.find('style').remove(); // remove any existing directory index styles
  4034. $iframe_head.append('<style id="iframe_dir_styles">'+ $iframe_dir_styles +'</style><style id="color_and_background_styles">'+ $color_and_background_styles +'</style>');
  4035. let iframe_table = ContentIframeDirEls(parent_link); // get iframe directory UI elements
  4036. const prepped_index = makeNewIndex($iframe_body, query_str.get('sort_by')); // prepare directory index
  4037. $iframe_body.removeAttr('style').addClass( body_classes.join(' ') ).empty().append(iframe_table).find('#tbody').append(prepped_index); // append UI and prepped index
  4038. }
  4039. }
  4040. // IFRAME Directory sorting
  4041. function iframeSorting(el) {
  4042. const $iframe_dir_list_rows = $('#iframe_body').find('#tbody').find('tr');
  4043. const id = el.attr('id'); // = new sort class
  4044. let current_sort = document.getElementById('iframe_body').classList.value.match(/sort_by_\w+/)[0];
  4045. if ( $('#iframe_body').data('sorting') !== id ) { // if clicking sorting item for the first time
  4046. $('#iframe_body').data('sorting',id).data('sort_direction', 1 );
  4047. el.removeClass('up');
  4048. } else { // clicking the same sorting item again -- reverse sort order
  4049. $('#iframe_body').toggleClass('sort_direction_default sort_direction_up').data('sort_direction', $('#iframe_body').data('sort_direction') * -1 );
  4050. el.toggleClass('up');
  4051. }
  4052. const sort_direction = $('#iframe_body').data('sort_direction');
  4053. const $sorted_index = sortDirList( $iframe_dir_list_rows, id, sort_direction );
  4054. $('#iframe_body').removeClass(current_sort).addClass(id).find('#tbody').empty().append($sorted_index);
  4055. }
  4056. $('#iframe_body').on('click','.sorting', function(e) { e.preventDefault(); iframeSorting($(this)); });
  4057. // IFRAME Click
  4058. $('#iframe_body, #iframe_body #toolbar, #iframe_body #toolbar li, #iframe_body #tbody').on('click', function() {
  4059. $('body').removeClass('is_blurred'); sendMessage('top','iframe_click'); // tell top to close menus, focus content
  4060. });
  4061. // IFRAME Select iframe row on click or play/pause iframe audio
  4062. function iframeSelectRow(row) {
  4063. $('body').removeClass('is_blurred');
  4064. row.addClass('selected').removeClass('is_blurred').siblings('tr').removeClass('is_blurred selected'); // set selected classes
  4065. if ( row.hasClass('audio') && row.hasClass('playing') ) {
  4066. sendMessage('top','iframe_play_pause_media'); // play/pause media
  4067. }
  4068. }
  4069. $('#iframe_body').on('click','#tbody tr', function(e) { e.preventDefault(); iframeSelectRow($(this)); });
  4070. // IFRAME Click links from html files (should really combine with iframeDoubleClickRow())
  4071. function iframeClickLink(e,link,id) {
  4072. let url, kind = getItemKind(getItemExt(link));
  4073. if ( !link.startsWith('#') ) { e.preventDefault(); url = newURL(link); }
  4074. switch(true) {
  4075. case id === 'tbody':
  4076. case url === undefined:
  4077. case link.startsWith('#'): // default link fragment behavior
  4078. break;
  4079. case id === 'parent': // open parent directory in iframe
  4080. sendMessage('top','show_iframe_dir','',[link + window.location.search,'dir']); // adds data:iframe_link to $content_iframe
  4081. break;
  4082. case url.protocol === 'file:' && window.location.protocol !== 'file:': // show warning when attempting to open local links from non-local pages
  4083. alert("Can't open local links from non-local pages.");
  4084. break;
  4085. case url.protocol !== 'file:' && window.location.protocol === 'file:': // open remote link from local file in new tab/window
  4086. window.open(link,'_blank');
  4087. break;
  4088. case url.protocol === 'file:' && window.location.protocol === 'file:': // open local links on local files in iframe
  4089. case url.protocol === 'about:': // document #link fragments
  4090. case RegExp(url.hostname).test(window.location.hostname): // same origin links (might not include TLD) (just covering bases here)
  4091. case RegExp(window.location.hostname).test(url.hostname): // same origin links (might not include TLD) (just covering bases here)
  4092. if ( /dir|app/.test(kind) ) { sendMessage('top','show_iframe_dir','',[link + getLinkQueries('dir'),kind] ); } else { sendMessage('top','show_iframe_file','',[link,kind] ); }
  4093. break;
  4094. default:
  4095. window.open(link,'_blank'); // else open external document links in new tab
  4096. break;
  4097. }
  4098. }
  4099. $('#iframe_body').on('click','a', function(e) { iframeClickLink(e,$(this).attr('href'),$(this).closest('span,tbody').attr('id')); });
  4100. // IFRAME Doubleclick iframe dir_list items (files and dirs)
  4101. function iframeDoubleClickRow(row) {
  4102. let kind = row.closest('tr').attr('data-kind'); // get item kind
  4103. if ( kind === 'audio' ) { row.closest('tr').addClass('playing selected').siblings('.audio').removeClass('playing selected'); }
  4104. let link = row.attr('href'); // get item link
  4105. switch(true) {
  4106. case ( /dir|app/.test(kind) ): // clicked item is a dir
  4107. sendMessage('top','show_iframe_dir','',[link + window.location.search,kind]); break;// tell top what to open; ignore ignored files when dblclicked
  4108. default: // clicked is a file
  4109. sendMessage('top','show_iframe_file','',[link,kind]); // tell top what to open; ignore ignored files when dblclicked
  4110. }
  4111. }
  4112. $('#iframe_body').on('dblclick','#tbody tr:not(.ignored) a', function(e) { e.preventDefault(); e.stopPropagation(); iframeDoubleClickRow($(this)); });
  4113. // Open IFRAME directory in sidebar
  4114. function openDirInSidebar() { let link = decodeURIComponentSafe($('#open_in_sidebar').find('a').attr('href')); sendMessage('top','open_iframe_dir_in_sidebar','',link); }
  4115. $('#open_in_sidebar').on('click',function(e) { e.preventDefault(); openDirInSidebar(); });
  4116.  
  4117. //***** TEXT EDITING PANE *****//
  4118. function setUpTextEditorUI() {
  4119. let source_text, body_classes = [];
  4120. if ( !$('body').hasClass('has_text_editor_UI') ) { // add classes, styles, and scripts; only add once
  4121. $('head').append('<style id="text_editor_styles">'+ $text_editor_styles +'</style>');
  4122. $('head').append('<link id="github_markdown_css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>');
  4123. $('#title span').empty().text('Text Editor');
  4124. }
  4125. switch(true) { // get source text and append UI elements
  4126. case window.self === window.top: // top level text editor
  4127. if ( !$('body').hasClass('has_text_editor_UI') ) {
  4128. $('body').addClass('has_text_editor_UI');
  4129. $('#content_text').append( TextEditingUIEls() );
  4130. if ( getQuery('sync_scroll') === 'true' ) { $('#sync_scroll input').prop({checked:true}); }
  4131. focusTextEditorPanes();
  4132. }
  4133. $('#content_pane').on('click','#content_text',function(){ $('body').addClass('focus_content'); });
  4134. break;
  4135. case window.self !== window.top: // iframe text editing UI
  4136. $('head').prepend('<meta charset="utf-8" /><meta http-equiv="Content-Type" content="text/plain; charset="utf-8">');
  4137. source_text = decodeURIComponentSafe($('body').find('> pre').text()); // get source text and decode Unicode chars.
  4138. body_classes.push('has_text_editor_UI');
  4139. if ( getQuery('disable_text_editing') === 'true' ) {
  4140. $body.removeClass('split_view source_text');
  4141. body_classes.push('text_editing_disabled preview_text');
  4142. } else {
  4143. if ( getQuery('split_view') === 'true' ) { body_classes.push('split_view'); }
  4144. if ( getQuery('default_text_view') === 'preview_text' ) { body_classes.push('preview_text'); } else { body_classes.push('source_text'); }
  4145. }
  4146. if ( getQuery('editor_theme') !== '') { body_classes.push('editor_theme_'+ getQuery('editor_theme')); } else { body_classes.push('editor_theme_'+ getQuery('theme')); }
  4147. // add text editor ui and warnings
  4148. $('head').append('<style id="warning_styles">'+ $warning_styles +'</style>');
  4149. $('body').addClass( body_classes.join(' ') )
  4150. .empty().append('<div id="content_text" class="background_color_DD_33">'+ TextEditingUIEls() +' </div><div id="warnings_container" class="">'+ Warnings() +'</div>');
  4151. $('#text_source').val(source_text); // set the source text value
  4152. if ( getQuery('sync_scroll') === 'true' ) { $('#sync_scroll input').prop({checked:true}); }
  4153. break;
  4154. }
  4155. TextEditing(); // call text editing functions
  4156. }
  4157. // setup and show top level text editor
  4158. $('#text_editor, #text_editor_row').on('click', function(e) { e.preventDefault(); showContent( $(this).attr('id') ); });
  4159.  
  4160. // Main Text Editing Function
  4161. function TextEditing() {
  4162. let $toolbar = $('#toolbar'), $source = $('#text_source'), $preview = $('#text_preview'), $html = $('#html_preview'), $MDhandle = $('#text_editing_handle');
  4163. // Toolbar button functions
  4164. $toolbar.on('mousedown', function(e) { e.preventDefault(); }); // prevent textarea from losing focus when clicking sidebar
  4165. $toolbar.on('click','li,span', function(e) { e.stopPropagation(); MDtoolBarFunctions($(this).attr('id')); });
  4166. // Resize
  4167. $MDhandle.on('mousedown', function(e) { e.stopPropagation(); MDresizeSplit($MDhandle,$source); });
  4168. $(window).on('resize', function() { $source.add($preview).add($MDhandle).attr('style',''); }); // reset split to 50/50 on window resize;
  4169. // Click labels to toggle checkboxes
  4170. $preview.add($toolbar).on('click','label', function(e) { e.stopPropagation(); $(this).siblings('input').click(); });
  4171. // Sync scroll
  4172. $source.on('scroll', function() { MDsyncScroll(this); });
  4173. $preview.on('scroll', function() { MDsyncScroll(this); });
  4174. $html.on('scroll', function() { MDsyncScroll(this); });
  4175. // Generate Markdown Preview
  4176. let source_text = ( $source.length === 0 ? '' : $source.val() );
  4177. MDmarkdown( source_text, $preview );
  4178. // Live preview update, and set edited classes for unsaved warning
  4179. $source.on('input', function() { // only add class or send message once after editing
  4180. if ( !$('body').hasClass('edited') ) {
  4181. $('body').addClass('edited'); // add edited class
  4182. if ( window.top !== window.self ) { sendMessage('top','iframe_edited','',''); } // send edited message to top
  4183. }
  4184. MDlivePreview($source,$preview);
  4185. });
  4186. // Checklists
  4187. MDsetChecklistClass();
  4188. $preview.on('click','.checklist input', function(e) { e.stopPropagation(); MDliveCheckBoxes($(this),$source,$preview); }); // Live checkboxes
  4189. $preview.on('click','.table-of-contents a', function(e) { e.preventDefault(); MDtocClick($(this),$preview); }); // Preview TOC click navigation
  4190. $preview.on('click','.uplink', function(e) { e.stopPropagation(); MDheaderClick($preview); }); // Click header uplinks
  4191. }
  4192. ///// END MAIN MD FUNCTION
  4193.  
  4194. // MARKDOWN Functions
  4195. // Focus Text
  4196. function focusTextEditorPanes() {
  4197. switch(true) {
  4198. case $('body').hasClass('split_view'): case $('body').hasClass('source_text'): $('#text_source').focus(); break;
  4199. case $('body').hasClass('preview_html'): $('#html_preview').focus(); break;
  4200. case $('body').hasClass('preview_text'): $('#text_preview').focus(); break;
  4201. }
  4202. }
  4203. // toggle text editor panes (on tab)
  4204. function toggleTextEditorPanes() {
  4205. switch(true) {
  4206. case document.activeElement.id === 'text_preview' && getFocusableEls('#text_preview').length > 0: // focus focusable elements in text preview
  4207. getFocusableEls('#text_preview').first().focus(); break;
  4208. case ( /text_preview|html_preview|text_source]/.test(document.activeElement.id) && !$('body').hasClass('split_view') ): // text editor: if not split view, focus sidebar
  4209. sendMessage('top','focus_sidebar'); break;
  4210. case $('body').hasClass('split_view'):
  4211. switch(true) {
  4212. case document.activeElement.id === 'text_source': // text editor: if text source has focus with split, focus the other pane
  4213. if ( $('body').hasClass('preview_html') ) { $('#html_preview').focus(); } else { $('#text_preview').focus(); } break;
  4214. case ( /text_preview|html_preview/.test(document.activeElement.id) ): // text editor: if text preview has focus with split, focus text source
  4215. $('#text_source').focus(); break;
  4216. }
  4217. break;
  4218. }
  4219. }
  4220. // Restore text selection on focus
  4221. $('#content_text').on('focus','#text_source',function() {
  4222. let selection_start, selection_end;
  4223. switch(true) {
  4224. case window.self === window.top:
  4225. selection_start = $('#text_source').attr('data-selection_start');
  4226. selection_end = $('#text_source').attr('data-selection_end');
  4227. break;
  4228. case window.self !== window.top:
  4229. selection_start = $('#text_source').attr('data-selection_start');
  4230. selection_end = $('#text_source').attr('data-selection_end');
  4231. }
  4232. if ( selection_start !== undefined && selection_end !== undefined ) {
  4233. document.getElementById('text_source').setSelectionRange(selection_start, selection_end); //, selectionDirection
  4234. }
  4235. });
  4236. // Save text range or cursor location
  4237. $('#content_text').on('blur','#text_source',function() {
  4238. let selection_start = $(this).prop('selection_start');
  4239. let selection_end = $(this).prop('selection_end');
  4240. if ( window.self === window.top ) {
  4241. $(this).attr('data-selection_start',selection_start).attr('data-selection_end',selection_end);
  4242. } else {
  4243. sendMessage('top','save_text_selection','',[selection_start,selection_end]);
  4244. }
  4245. });
  4246. // Select Textarea Content
  4247. function selectTextareaContent(id) {
  4248. let $textarea = document.getElementById(id);
  4249. $textarea.focus();
  4250. $textarea.select();
  4251. $textarea.scrollTop = 0;
  4252. }
  4253. function MDtoolBarFunctions(id) { // MD UI Buttons functions
  4254. switch (id) {
  4255. case 'toggle_theme': $('body').toggleClass('editor_theme_dark editor_theme_light'); $('#text_source').focus(); break;
  4256. case 'toggle_split':
  4257. $('body').toggleClass('split_view').find('#text_source,#text_preview,#html_preview,#text_editing_handle').attr('style','');
  4258. focusTextEditorPanes();
  4259. break;
  4260. case 'show_source':
  4261. $('body').toggleClass('split_view').removeClass('preview_text').addClass('source_text');
  4262. focusTextEditorPanes();
  4263. break;
  4264. case 'show_preview':
  4265. $('body').toggleClass('split_view').removeClass('source_text').addClass('preview_text');
  4266. focusTextEditorPanes();
  4267. break;
  4268. case 'show_html': // toggle html and preview
  4269. $('body').removeClass('source_text');
  4270. if ( $('body').hasClass('preview_html') ) {
  4271. $('body').removeClass('preview_html');
  4272. if ( document.getElementById('text_source') !== document.activeElement ) { $('#text_preview').focus(); }
  4273. } else {
  4274. let source_HTML = $('#text_preview').html().toString();
  4275. $('#html_preview').empty().text(source_HTML);
  4276. $('body').addClass('preview_html');
  4277. if ( document.getElementById('text_source') !== document.activeElement ) { $('#html_preview').focus(); }
  4278. }
  4279. break;
  4280. case 'clear_text':
  4281. if ( $('#text_source').val().length > 0 ) {
  4282. $('body').addClass('has_warning').find('#warnings_container').removeClass().addClass('clear');
  4283. focusButton('warning_btn_cancel');
  4284. } else {
  4285. focusTextEditorPanes();
  4286. }
  4287. break;
  4288. case 'save_text_link':
  4289. case 'save_HTML_link':
  4290. saveBtn(id);
  4291. break;
  4292. }
  4293. }
  4294. function saveBtn(id) {
  4295. let data, ext, file_name;
  4296. if ( $('#content_pane').attr('data-content') === 'has_text_editor' ) {
  4297. file_name = 'untitled';
  4298. } else {
  4299. file_name = decodeURIComponentSafe(window.location.pathname.split('/').reverse()[0]);
  4300. file_name = file_name.slice(0,file_name.lastIndexOf('.'));
  4301. }
  4302. if ( id === 'save_text_link' ) {
  4303. data = $('#text_source').val(); ext = '.md';
  4304. } else {
  4305. data = MDprepHTML$('#text_preview').val(); ext = '.html';
  4306. }
  4307. saveMD( data, file_name + ext );
  4308. }
  4309. // MD SAVE SOURCE or HTML
  4310. function MDprepHTML(data) {
  4311. const save_HTML_open = `<!DOCTYPE html><html><head><meta charset="utf-8" /><title></title>
  4312. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css"></link>
  4313. <style></style><script></script></head><body lang="en" class="markdown-body">`;
  4314. const save_HTML_close = '</body></html>';
  4315. data = data.replace(/<span\sclass="uplink">.<\/span>/g,'');
  4316. return save_HTML_open + data + save_HTML_close;
  4317. }
  4318. function saveMD(data,file_name) { // #top must save text, otherwise a new window is opened that contains the blob content
  4319. if ( window.top !== window.self ) { sendMessage('top','save_text','',[data,file_name]); } else { saveFile(data,'text/plain',file_name); }
  4320. $('body,#text_source,#content_text').removeClass('edited');
  4321. }
  4322. // MD Custom pre- and post-processing for text.
  4323. function MDaddHeaderIDs(match, p1, p2, p3) { return '<h'+ p1 +' id="'+ p3.toLowerCase().replace(/\s/g,'-') +'" ' + p2 +'>'+ p3; } // create header ids for TOC
  4324. function MDcustomPreProcess(src) { return src; } // we're not doing anything here just yet...
  4325. function MDcustomPostProcess(html) {
  4326. 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
  4327. .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
  4328. // .replace(/<li><p class="checklist">"/g,'<li class="checklist"><p>')
  4329. .replace(/^<h(\d)([^>]*)>([^<]+)/gm, MDaddHeaderIDs) // add header IDs;
  4330. .replace(/<\/h(\d)>/g,'<span class="uplink">&uarr;</span></h$1>');
  4331. return html;
  4332. }
  4333. //MD Render markdown from preprocessed source text
  4334. function MDmarkdown(source_text,$preview_el) {
  4335. const MDit = window.markdownit({linkify:false, typography:false, html:true})
  4336. .use(window.markdownitMultimdTable, {enableMultilineRows: true})
  4337. .use(window.markdownitSub)
  4338. .use(window.markdownitSup)
  4339. .use(window.markdownitFootnote)
  4340. .use(window.markdownitCentertext)
  4341. .use(window.markdownitDeflist)
  4342. .use(window.markdownitTocDoneRight)
  4343. ;
  4344. let MDpreview = MDit.render( MDcustomPreProcess( source_text ) );
  4345. $preview_el.html( MDcustomPostProcess( MDpreview ) ); // set previewed html
  4346. }
  4347. // MD Live preview, add edited warning
  4348. function MDlivePreview($source_el,$preview_el) {
  4349. MDmarkdown( $source_el.val(),$preview_el );
  4350. MDsetChecklistClass();
  4351. let source_HTML = $preview_el.html().toString();
  4352. $('#html_preview').empty().text(source_HTML);
  4353. }
  4354. // MD Live Checkboxes prep: find each instance of [ ] or [x] and replace text in index = to clicked checkbox in Preview.
  4355. function MDreplaceAt(str, replacement, position) { str = str.substring(0, position) + replacement + str.substring(position + replacement.length); return str; }
  4356. function MDreplaceNthSubStr(str,substr,replacement,index) {
  4357. let count = 0;
  4358. let found = substr.exec(str);
  4359. while ( found !== null ) {
  4360. if ( count === index ) { return MDreplaceAt(str, replacement, found.index ); } else { count++; found = substr.exec(str); }
  4361. }
  4362. }
  4363. // MD Live Checkboxes
  4364. function MDliveCheckBoxes(checkbox,$source_el,$preview_el) {
  4365. $('.checklist').removeClass('clicked');
  4366. checkbox.closest('p,li,dt,dd').addClass('clicked');
  4367. const this_index = $preview_el.find('.checklist').index( $('.clicked') );
  4368. const src_text = $source_el.val();
  4369. const substr = new RegExp(/\[\s*.\s*\]/g);
  4370. const replacement = ( checkbox.is(':checked') ? '[x]' : '[ ]' );
  4371. $source_el.val( MDreplaceNthSubStr(src_text, substr, replacement, this_index) );
  4372. }
  4373. // MD Checkbox list class: Prevent checkbox lists from having list bullets
  4374. function MDsetChecklistClass() { $('input[type="checkbox"]').closest('ul').css({'list-style':'none','padding':'0'}); }
  4375. // MD Resize Split View
  4376. function MDresizeSplit(handle,$source_el) {
  4377. let sidebar_width = $('#sidebar').outerWidth();
  4378. let page_width = window.innerWidth;
  4379.  
  4380. $(document).on('mousemove',function(e) {
  4381. e.stopPropagation(); e.preventDefault();
  4382. let pageX = e.pageX;
  4383. if ( pageX > sidebar_width + 100 && pageX < page_width - 100 ) { // min widths
  4384. $('#text_source').css({'width': pageX - sidebar_width + 'px'});
  4385. $('#html_preview').css({'width': $('#content_text').outerWidth() - $source_el.outerWidth() + 'px'});
  4386. $('#text_preview').css({'width': $('#content_text').outerWidth() - $source_el.outerWidth() + 'px'});
  4387. handle.css({'left': $source_el.outerWidth() - 4 + 'px'});
  4388. }
  4389. });
  4390. handle.on('mouseup', function() { $(document).off('mousemove'); focusTextEditorPanes(); });
  4391. }
  4392. // MD UI Sync Scroll
  4393. function MDpercentage(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)); }
  4394. function MDsyncScroll(el1) {
  4395. let el2; // scroll this element when scrolling el1
  4396. switch(true) {
  4397. case el1.getAttribute('id') === 'text_source':
  4398. el2 = ( $('body').hasClass('preview_html') ? document.getElementById('html_preview') : document.getElementById('text_preview') );
  4399. break;
  4400. case el1.getAttribute('id') === 'text_preview': case el1.getAttribute('id') === 'html_preview':
  4401. el2 = document.getElementById('text_source');
  4402. break;
  4403. }
  4404. if ( document.querySelector('input[name="sync_scroll"').checked ) {
  4405. el2.scrollTo( 0, (MDpercentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
  4406. }
  4407. }
  4408. // click TOC anchors
  4409. function MDtocClick(el,$preview_el) { let thisId = el.attr('href'); if ( thisId ) { $preview_el.scrollTop( $(thisId).offset().top - 48 ); } }
  4410. // click Headers to return to TOC or top
  4411. function MDheaderClick($preview_el) {
  4412. if ( $preview_el.find('.table-of-contents').length > 0 ) {
  4413. document.getElementsByClassName('table-of-contents')[0].scrollIntoView(true);
  4414. } else {
  4415. document.getElementById('preview').scroll(0,0);
  4416. }
  4417. }
  4418. // MD Clear text source
  4419. function clearText() {
  4420. if ( window.self !== window.top ) { sendMessage('top','iframe_edited'); }
  4421. $('body').removeClass('edited');
  4422. $('#text_preview,#html_preview').empty();
  4423. $('#text_source').val('').show().focus();
  4424. }
  4425. // END TEXT EDITING
  4426.  
  4427. // MESSAGES
  4428. // Send a message to iframe or parent
  4429. function sendMessage(target,message,funcName,args) {
  4430. var messageObj = { 'messageContent': message, 'functionName': funcName, 'arguments': args };
  4431. if ( target === 'iframe' ) {
  4432. let contentIFrame = document.getElementById('content_iframe');
  4433. contentIFrame.contentWindow.postMessage( messageObj, '*' );
  4434. }
  4435. if ( target === 'top' ) {
  4436. window.parent.postMessage( messageObj, '*');
  4437. }
  4438. }
  4439. // Receive a message from iframe or parent, do appropriate action
  4440. function receiveMessage(e) {
  4441. if ( e.origin === 'null' || e.origin === $origin ) {
  4442. let message = e.data.messageContent, funcName = e.data.functionName, args = e.data.arguments;
  4443. switch( message ) {
  4444. case 'arrowNavigation': arrowNavigation(args); break; // class_name, key
  4445. case 'toggle_sidebar': $('#toggle_sidebar').click(); break;
  4446. case 'toggle_menu': $('#menu_container').click(); break;
  4447. case 'close_menu': // no break
  4448. case 'top_closed_menu': $('#iframe_body').removeClass('has_menu is_blurred'); break;
  4449. case 'top_has_menu': $('#iframe_body').addClass('has_menu is_blurred'); break; // tell iframe top has menu
  4450. case 'menu_navigation': menuNavigation(args); break; // menu navigation from iframe
  4451. case 'menu_selection': // no break
  4452. case 'clickMenu': e.preventDefault(); e.stopPropagation(); clickMenu(); break; // show menu
  4453. case 'toggle_invisibles': $('#show_invisibles_container').find('input').click(); focusContent(); break;
  4454. case 'focus_sidebar': focusSidebar(); // no break
  4455. case 'escape': // no break
  4456. case 'tab': // close menus and refocus content or focus sidebar
  4457. switch(true) { case $('#top').hasClass('focus_content') && $('#top').hasClass('has_menu'): focusContent(); break; default: focusSidebar(); } scrollThis('tbody','selected'); break;
  4458. case 'shift_focus_iframe':
  4459. switch(true) {
  4460. case $('#iframe_dir_list_wrapper').length === 1: // if iframe dir_list visible...
  4461. $('body').removeClass('is_blurred');
  4462. switch(true) {
  4463. case $('tr.selected, tr.is_blurred').length > 0: $('tr.is_blurred').removeClass('is_blurred').addClass('selected'); break;
  4464. default: $('#tbody').find('tr:visible').last().addClass('selected'); // select last row when tabbing into directory
  4465. }
  4466. break;
  4467. default: getFocusableEls('#iframe_body').last().focus(); break;
  4468. }
  4469. scrollThis('tbody','selected');
  4470. break;
  4471. case 'focus_iframe': // after tabbing into iframe
  4472. $('body').removeClass('has_menu is_blurred');
  4473. switch(true) {
  4474. case $('#iframe_dir_list_wrapper').length === 1: // if iframe dir_list visible...
  4475. switch(true) {
  4476. case $('tr.selected, tr.is_blurred').length > 0: $('tr.is_blurred').removeClass('is_blurred').addClass('selected'); break;
  4477. default: $('#tbody').find('tr:visible').first().addClass('selected'); // select first row when tabbing into directory
  4478. }
  4479. scrollThis('tbody','selected');
  4480. break;
  4481. case $('body').hasClass('has_text_editor_UI'): // if text editor visible...
  4482. switch(true) { case document.activeElement.id === 'iframe_body': focusTextEditorPanes(); break; }
  4483. switch(true) { // ... and restore text selection: NOT IMPLEMENTED YET -- but should only be used for text editor, not editable file?
  4484. case $('body').hasClass('split_view'):
  4485. case $('body').hasClass('source_text'): {
  4486. let selection = window.getSelection();
  4487. if ( selection.anchorOffset > 0 ) { // restore cursor position or text selection
  4488. // document.getElementById('text_source').setSelectionRange(x,y);
  4489. // document.getElementById('text_source').scrollTop = [position of x];
  4490. // return;
  4491. } else { document.getElementById('text_source').setSelectionRange(0,0); document.getElementById('text_source').scrollTop = 0; }
  4492. break;
  4493. }
  4494. }
  4495. break;
  4496. case document.activeElement.id === 'iframe_body': // focus form elements and textareas in iframe files
  4497. getFocusableEls('#iframe_body').first().focus();
  4498. }
  4499. break;
  4500. case 'blur_iframe': $('body').addClass('is_blurred'); break;
  4501. case 'iframe_click': closeMenus(); $('body').addClass('focus_content'); break; // close menus and fade sidebar
  4502. case 'show_iframe_dir': showContent('content_iframe_dir',args); break; //args[0] === item link, args[1] === item kind
  4503. case 'show_iframe_file': showContent('content_iframe_file',args); break; //args[0] === item link, args[1] === item kind
  4504. case 'open_iframe_dir_in_sidebar': openIframeDirInSidebar(args); break; // tell top to open iframe directory in sidebar; args === iframe directory url
  4505. case 'open_iframe_parent_dir': $('#iframe_body #parent').find('a').click(); break;
  4506. case 'iframe_arrow_navigation':
  4507. switch(args) {
  4508. case 'ArrowUp': $('#tbody').find('tr:visible').last().find('a').click(); break;
  4509. case 'ArrowDown': $('#tbody').find('tr:visible').first().find('a').click(); break;
  4510. }
  4511. break;
  4512. case 'reload': $('#reload_btn').click(); break; // reload content
  4513. case 'close': $('#close_btn').click(); break; // escape content_iframe and close content
  4514. // toggle iframe dir_list UI prefs from main menu:
  4515. case 'show_numbers': case 'show_invisibles': case 'alternate_background': case 'hide_ignored_items': case 'ignore_ignored_items': $('#iframe_body').toggleClass(message); break;
  4516. // AUDIO MESSAGES
  4517. case 'iframe_play_pause_media': playPauseMedia(); break; // tell top to play/pause audio from iframe click
  4518. case 'mediaSkip': mediaSkip(undefined,args); break; // tell top to mediaskip from focused iframe
  4519. case 'play_prev_next_iframe_audio': // play next iframe track
  4520. if ( args === 'ArrowRight' ) {
  4521. if ( $('.audio.playing').nextAll('.audio.selected').length === 1 ) {
  4522. $('.audio.playing').nextAll('.audio.selected').first().find('a').dblclick();
  4523. } else {
  4524. $('.audio.playing').nextAll('.audio').first().find('a').dblclick();
  4525. }
  4526. }
  4527. break;
  4528. case 'close_iframe_audio': $('.playing').removeClass('playing'); break;
  4529. // TEXT EDITING MESSAGES
  4530. case 'iframe_edited': if ( !$('body#top').hasClass('iframe_edited') ) { $('body#top').addClass('iframe_edited'); } break; // let top know iframe text has been edited
  4531. case 'clear': $('body#top').addClass('iframe_edited'); break; // add edited class after clearing text from edited iframe file
  4532. case 'save_text_selection': $('.text.selected,.code.selected,.markdown.selected').attr('data-selection_start',args[0]).attr('data-selection_end',args[1]); break; // from iframe
  4533. case 'get_text_selection': $('#content_text').attr('data-selection_start',args[0]).attr('data-selection_end',args[1]); break;
  4534. case 'save_text': saveFile(args[0],'text/plain',args[1]); break;
  4535. // MESSAGES FROM TOP to IFRAME
  4536. case 'theme_light': case 'theme_dark': $('#iframe_body').toggleClass('theme_dark theme_light'); break;
  4537. case 'split_view': case 'toggle_split': $('#toggle_split').click(); break;
  4538. case 'default_text_view': $('#iframe_body').toggleClass('preview_text source_text').removeClass('split_view'); break;
  4539. case 'toggle_text_editor': showTextEditor(); break;
  4540. case 'unloading': // warn iframe that user wants to change or close iframe
  4541. if ( !$('#iframe_body').hasClass('has_warning') ) {
  4542. $('#iframe_body').addClass('has_warning').find('#warnings_container').removeClass().addClass('unloading').attr('data-function_name',funcName).attr('data-args',args); }
  4543. break;
  4544. case 'dont_save':// from iframe "Don't Save" button
  4545. $body.removeClass('iframe_edited'); $('tr.selected:not(.audio),tr.loaded:not(.audio)').removeClass('selected loaded'); closeContent(); focusSidebar(); break;
  4546. case 'open_link_file': openLink(args); break; // open webloc and url files
  4547. case 'show_content': // mainly for opening webloc and url files
  4548. if ( args[1] === 'dir' ) { args[0] = args[0] + getLinkQueries('dir'); showContent('content_iframe_dir',args); } else { showContent('content_iframe_file',args); } break;
  4549. case 'iframe_loaded': showIframeContent(args); break; // if message received by top, iframe loaded successfully; otherwise, data-loaded remains 'unloaded'
  4550. }
  4551. }
  4552. }
  4553. window.addEventListener('message',receiveMessage,false);
  4554. // END MESSAGES
  4555.  
  4556. // WARNINGS
  4557. // list of functions to remember while sending messages and then execute after warning button click
  4558. function doFunction(funcName,args) {
  4559. var funcDictionary = { 'arrowNavigation':arrowNavigation, 'clickRow':clickRow, 'doubleClickRow':doubleClickRow, 'null':null, 'clickMenu':clickMenu, 'clickThis':clickThis, 'clearText':clearText,
  4560. 'closeButton':closeButton, 'closeContent':closeContent, 'closeFontFile':closeFontFile, 'closePlaylist':closePlaylist, 'closeGlyph':closeGlyph, 'mediaSkip':mediaSkip,
  4561. 'openSidebarInContentPane':openSidebarInContentPane, 'resetContent':resetContent, 'setLocation':setLocation, 'showDirectorySource':showDirectorySource };
  4562. return funcName === 'null' ? null : funcDictionary[funcName](args);
  4563. }
  4564. // Open and Close Warning alert, focus default button
  4565. function openWarning(id,buttonid) { $('body').addClass('has_warning').find('#warnings_container').removeClass().addClass(id); focusButton(buttonid); }
  4566. function closeWarning() { $('body').removeClass('has_warning').find('#warnings_container, #warning_buttons button').removeClass(); }
  4567. // Show warning after in certain conditions (edited text, open playlist, open font file, etc.; otherwise do the action.
  4568. function showWarning(funcName,args) {
  4569. switch(true) {
  4570. case ( /arrowNavigation|clickRow/.test(funcName) ): // warnings for arrow navigation and row clicks
  4571. switch(true) {
  4572. // upon receipt of message, iframe will show its warning message, based on the funcName
  4573. case $('body').hasClass('iframe_edited'): sendMessage('iframe','unloading',funcName,args.key); focusButton('warning_btn_save'); break;
  4574. // warn with open font file and focused sidebar
  4575. case !$('body').hasClass('focus_content') && /has_font_file|has_glyph/.test( $content_pane.attr('data-content') ): openWarning('warning_close_font','warning_btn_cancel'); break;
  4576. default: doFunction(funcName,args); break;
  4577. }
  4578. break;
  4579. case ( !/arrowNavigation|clickRow/.test(funcName) ): // warnings for other functions
  4580. switch(true) {
  4581. case $content_pane.attr('data-content') === 'has_font_file': openWarning('warning_close_font','warning_btn_cancel'); break; // warn with open font file and close button.
  4582. case $('body').hasClass('has_playlist') && $content_pane.attr('data-content') === undefined && !$content_pane.hasClass('has_audio'):
  4583. case $('body').hasClass('has_filelist') && $content_pane.attr('data-content') === undefined && !$content_pane.hasClass('has_audio'):
  4584. openWarning('warning_close_playlist','warning_btn_cancel'); break;
  4585. case $('body').hasClass('edited'):
  4586. $('#content_pane').removeClass('has_hidden_text_editor').attr('data-content','has_text_editor'); openWarning('unloading','warning_btn_save'); break;
  4587. default: doFunction(funcName,args); break;
  4588. }
  4589. }
  4590. }
  4591. // Warning buttons: what to do when the user clicks a warning button
  4592. function warningButtons(id) {
  4593. let btn = $(document.getElementById(id)), container_el = btn.closest('body');
  4594. switch(id) {
  4595. case 'warning_btn_dont_save': // do the user initiated func without saving the edited text
  4596. switch(true) {
  4597. case window.self !== window.top:
  4598. if ( $('#warnings_container').hasClass('unloading') ) { clearText(container_el); sendMessage('top','dont_save'); } break;// remove the irame src and body.iframe_edited class ignore
  4599. case window.self === window.top:
  4600. clearText(container_el); $content_pane.removeAttr('data-content'); $('#content_iframe').removeAttr('src').removeClass('has_content'); $dir_list.find('.dir.selected a').click();
  4601. }
  4602. closeWarning();
  4603. break;
  4604. case 'warning_btn_cancel': closeWarning(); if ( $body.hasClass('focus_content') ) { focusContent(); } break;
  4605. case 'warning_btn_clear': closeWarning(); clearText(); break; // clear text editor
  4606. case 'warning_btn_save': if ( window.top !== window.self ) { sendMessage('top','clear'); } container_el.removeClass('edited'); $('#save_text_link').click(); closeWarning(); break;
  4607. case 'warning_btn_ok':
  4608. switch(true) {
  4609. case $('#warnings_container').hasClass('warning_close_font'): closeFontFile(); closeWarning(); break;
  4610. case $('#warnings_container').hasClass('warning_close_playlist'): closePlaylist(); closeWarning(); break;
  4611. case $('#warnings_container').hasClass('warning_make_playlist'): makePlaylist(); break;
  4612. case $('#warnings_container').hasClass('warning_local_bookmark'): // no break
  4613. case $('#warnings_container').hasClass('warning_local_file'): // no break
  4614. case $('#warnings_container').hasClass('warning_local_playlist'): // no break
  4615. case $('#warnings_container').hasClass('warning_no_playlist'): closeWarning(); break;
  4616. }
  4617. break;
  4618. }
  4619. }
  4620. // Click Edited Warning Buttons
  4621. $('#warnings_container').on('click','button', function(e) { e.preventDefault(); e.stopPropagation(); warningButtons( $(this).attr('id') ); });
  4622. // Edited Warning overlay: prevent user clicks on rest of UI
  4623. $('body.has_overlay, body.has_warning').on('click mousedown mouseup', function(e) { e.preventDefault(); e.stopPropagation(); return; });
  4624. // Tab Warning Buttons (keyboard event)
  4625. function tabWarningButtons(e) {
  4626. switch(true) {
  4627. case e.shiftKey:
  4628. if ( !$('#warning_buttons').find(':focus,.focus').length || !$('#warning_buttons').find(':focus,.focus').prevAll('button:visible').length ) {
  4629. $('#warning_buttons').find('button:visible').removeClass('focus').last().focus().addClass('focus');
  4630. } else {
  4631. $('#warning_buttons').find(':focus,.focus').removeClass('focus').prevAll('button:visible').first().addClass('focus').focus();
  4632. }
  4633. break;
  4634. default:
  4635. if ( !$('#warning_buttons').find(':focus,.focus').length || !$('#warning_buttons').find(':focus,.focus').nextAll('button:visible').length ) {
  4636. $('#warning_buttons').find('button:visible').removeClass('focus').first().focus().addClass('focus');
  4637. } else {
  4638. $('#warning_buttons').find(':focus,.focus').removeClass('focus').nextAll('button:visible').first().addClass('focus').focus();
  4639. }
  4640. }
  4641. }
  4642. // END WARNINGS
  4643.  
  4644. // PLAYLISTS
  4645. // Open playlist
  4646. $('#menu').on('click','#open_playlist_label', function(e) { e.stopPropagation(); });
  4647. $('#menu').on('change','#open_playlist', function(e) { openFile(e,'playlist'); });
  4648. // Create Playlist items
  4649. function convertPlaylist(items) {
  4650. let prepped_index = ''; let prepped_row = ''; let rows, info, title, type, time = '0', display_time = '—', link, url, kind = '', display_kind, ext;
  4651. items = items.replace(/\s*#EXTM3U.*\s*/g,'').replace(/^\*\n{2,}/gm,'\n'); // remove header comment and multiple returns
  4652. switch(true) { // create rows based on m3u type;
  4653. case ( /#EXTINF:/.test(items) ): type = 'extm3u'; rows = items.split('#EXTINF:'); break; // need to add cases for other extm3u directives
  4654. default: type = 'm3u'; rows = items.split('\n'); break;
  4655. }
  4656. for ( let i = 0; i < rows.length; i++ ) {
  4657. let row = rows[i], classes = [];
  4658. switch(true) { // get entry information: title, link, etc.
  4659. case row.length === 0:
  4660. break;
  4661. case type === 'extm3u' && row.trim().length > 0: // extm3u file
  4662. row = row.trim().split('\n');
  4663. info = row[0];
  4664. time = info.slice(0,info.indexOf(',')).replace('undefined','');
  4665. display_time = new Date(time * 1000).toISOString().substr(11, 8);
  4666. if ( display_time === '00:00:00' ) { display_time = '—'; }
  4667. title = info.slice(info.indexOf(',') + 1);
  4668. link = row[1];
  4669. break;
  4670. default: // m3u with urls only
  4671. title = decodeURIComponentSafe(row.slice(row.lastIndexOf('/') + 1));
  4672. link = row;
  4673. break;
  4674. }
  4675. if ( link !== undefined ) { url = newURL(link); }
  4676. switch(true) {
  4677. case url === undefined && i !== 0: // add notice for invalid url and row class
  4678. closeMenus();
  4679. classes.push('ignored','invalid','disabled');
  4680. title += ' [invalid url]';
  4681. case url === undefined: break;
  4682. case url.search.length: // no break; remove query string from dirs
  4683. link = link.replace(/\/\?.+/,'/');
  4684. case url.pathname.endsWith('/'): // directory
  4685. ext = ' dir'; kind = 'dir'; classes.push('dir');
  4686. break;
  4687. case /file:/.test(link) && !/file:/.test($protocol):
  4688. classes.push('disabled'); // disable local files on non-local pages
  4689. case link.indexOf('pdf#') > -1: // remove pdf parameters
  4690. link = link.replace(/pdf#.+/,'pdf');
  4691. default: // files
  4692. ext = url.pathname.slice(url.pathname.lastIndexOf('.') + 1); kind = getItemKind(ext);
  4693. switch(true) {
  4694. case kind === 'audio':
  4695. $('body').addClass('has_media has_audio');
  4696. classes.push('file','media',kind,ext);
  4697. $body.removeClass('has_filelist').addClass('has_playlist');
  4698. break;
  4699. case kind === 'video':
  4700. $('body').addClass('has_media has_video');
  4701. classes.push('file','media',kind,ext);
  4702. $body.removeClass('has_filelist').addClass('has_playlist');
  4703. break;
  4704. case kind === 'image': $('body').addClass('has_images');
  4705. case kind === 'font': $('body').addClass('has_fonts');
  4706. default: classes.push('file',kind,ext); $body.removeClass('has_playlist').addClass('has_filelist');
  4707. }
  4708. }
  4709. let display_kind = kind.slice(0,1).toUpperCase() + kind.slice(1);
  4710.  
  4711. if ( i !== 0 ) { // don't list first item twice
  4712. prepped_row = '<tr id="rowid-'+ i +'" class="'+ classes.join(' ') +'" data-kind="'+ kind +'" data-ext="'+ ext +'"><td class="tbody_row_cell tbody_row_cell_name name" data-name="'+ title +'"><a class="tbody_row_cell_name_a icon text_color_111" href="'+ link +'"><span class="tbody_row_cell_name_a_span has_icon_before"><input type="checkbox" tabindex="-1" checked="true">'+ title +'</span></a></td><td class="tbody_row_cell_details size details" data-size="'+ time +'">'+ display_time +'</td><td class="tbody_row_cell_details kind details" data-kind="'+ kind +'">'+ display_kind +'</td><td class="tbody_row_cell_details ext details" data-ext="'+ ext +'"></td></tr>';
  4713. prepped_index += prepped_row;
  4714. }
  4715. }
  4716. return prepped_index;
  4717. }
  4718. // Open Playlist/Filelist
  4719. function openPlaylist(files,reader) {
  4720. if ( !$body.hasClass('has_playlist') && !$body.hasClass('has_filelist') ) { // store original dir_list and body "has_" classes as data if body does not already have playlist or filelist
  4721. let body_classes = document.getElementById('top').classList, data_classes = [];
  4722. for ( let bodyClass of body_classes.values() ) { if ( bodyClass.startsWith('has') ) { data_classes.push( bodyClass ); } } // add original body classes to dataclasses
  4723. $('#tbody').data('dir_list',$('#tbody').html() ).data('data_classes',data_classes);
  4724. $body.removeClass(data_classes.join(' '));
  4725. }
  4726. closeContent(); // close all existing content, because it shouldn't be navigable or selectable with playlist loaded
  4727. let prepped_index = convertPlaylist(reader.result); // get the new index
  4728. $('#tbody').empty().append(prepped_index); // append the prepared playlist
  4729. $('#top,#content_pane').removeClass('has_menu faded');
  4730. if ( !$('#sort_by_name').hasClass('selected' ) ) { $('#sort_by_name').click(); } // sort by name
  4731. $('#stats_summary_playlist_files').empty().html('<span>Playlist: '+ $('#tbody tr.media').length +' Files</span>'); // update stats
  4732. if ( /file:/.test(prepped_index) && !/file:/.test($protocol) ) { // show warning about local files on non-local page
  4733. $body.addClass('has_warning');
  4734. $('tbody').addClass('local');
  4735. openWarning('warning_local_playlist','warning_btn_ok');
  4736. }
  4737. if ( $body.hasClass('autoload_media') ) {
  4738. $('#tbody tr.media:not(.disabled)').first().click(); // select first item
  4739. if ( $body.hasClass('has_images') ) { autoLoadCoverArt(); }
  4740. }
  4741. scrollThis('tbody','selected',false);
  4742. document.title = 'Playlist: '+ files.name;
  4743. $('#parents_dir_nav').find('> div').empty().html( files.name );
  4744. $('#open_playlist').val('');
  4745. }
  4746. // Make and save playlist
  4747. function makePlaylistLink(link) {
  4748. link = link.replace(/\/\?.+/,'/').replace(/\.pdf\#.+/,'pdf');
  4749. switch(true) { // we need to handle various cases like this in case the link is being from a loaded playlist which might include links to other pages
  4750. case link.startsWith('http'): // no break; remote files
  4751. case link.startsWith('file'): link = link; break; // local files
  4752. case link.startsWith('/'): link = $protocol +'//' + link; break;
  4753. case !link.startsWith('file'): link = $protocol +'//'+ window.location.host + window.location.pathname + link; break;
  4754. }
  4755. return link;
  4756. }
  4757. function makePlaylist() {
  4758. let items = $('#tbody'), rows, playlist = [], playlistEntry = '';
  4759. let playlist_type = $('#make_playlist_form').find('input:checked').attr('id');
  4760. switch(playlist_type) {
  4761. case 'media_files_only': rows = items.children('tr.media:not(.unchecked)'); break;
  4762. case 'audio_files_only': rows = items.children('tr.audio:not(.unchecked)'); break;
  4763. case 'video_files_only': rows = items.children('tr.video:not(.unchecked)'); break;
  4764. case 'all_non_media_files': rows = items.children('tr:not(.media)'); break;
  4765. case 'all_items': rows = items.children('tr'); break;
  4766. case 'directories_only': rows = items.children('tr.dir'); break;
  4767. case 'files_only': rows = items.children('tr.file'); break;
  4768. }
  4769. switch(true) { // show warning if no qualifying items found or make playlist
  4770. case rows.length === 0:
  4771. openWarning('warning_no_playlist','warning_btn_ok');
  4772. break;
  4773. default:
  4774. for ( let i = 0; i < rows.length; i++ ) {
  4775. let row = $(rows[i]);
  4776. let link = row.find('a').attr('href');
  4777. link = makePlaylistLink(link);
  4778. let timing = '';
  4779. playlistEntry = '#EXTINF:'+ timing +','+ row.find('a').text() +'\n'+ link;
  4780. playlist.push(playlistEntry);
  4781. }
  4782. playlist = '#EXTM3U\n'+ playlist.join('\n'); // add playlist header id
  4783. saveFile(playlist,'audio/mpeg-url','untitled.m3u'); // show save file dialogue
  4784. closeWarning();
  4785. }
  4786. }
  4787. // Select "Make Playlist" menu item: show make playlist warning list
  4788. $('#make_playlist').on('click',function(e) {
  4789. e.preventDefault(); e.stopPropagation(); closeMenus(); openWarning('warning_make_playlist','warning_btn_ok');
  4790. });
  4791. // Get playlist entry
  4792. function makePlaylistEntry(id) {
  4793. let title, link, duration = '';
  4794. switch(true) {
  4795. case id === 'title': // make link for non-audio items
  4796. title = $('#title span').text();
  4797. switch(true) {
  4798. case $('.content.has_content').attr('id') === 'content_image_container': // link for images
  4799. link = $('.content.has_content').find('img').attr('src');
  4800. break;
  4801. case $('.content.has_content').attr('id') !== 'content_image_container': // all other content
  4802. link = $('.content.has_content').attr('src');
  4803. if ($('.content.has_content').attr('id') === 'content_video') { duration = Number.parseInt(getElById('video')[0].duration); }
  4804. break;
  4805. }
  4806. link = makePlaylistLink(link);
  4807. $('#content_playlist').find('textarea').val('#EXTINF:'+ duration +','+ title +'\n'+ link +'\n').focus();
  4808. break;
  4809. case id === 'content_audio_title':
  4810. title = $('#content_audio_title span').text();
  4811. link = $('#audio').attr('src');
  4812. link = makePlaylistLink(link);
  4813. duration = Number.parseInt(getElById('audio')[0].duration);
  4814. $('#content_audio_playlist').find('textarea').val('#EXTINF:'+ duration +','+ title +'\n'+ link +'\n').focus();
  4815. break;
  4816. }
  4817. }
  4818. // Show Playlist Entry
  4819. function showPlaylistEntry(id) {
  4820. makePlaylistEntry(id);
  4821. switch(true) {
  4822. case id === 'title':
  4823. document.getElementById('content_playlist').classList.toggle('has_content');
  4824. selectTextareaContent('content_playlist_textarea');
  4825. break;
  4826. case id === 'content_audio_title':
  4827. document.getElementById('content_audio_playlist').classList.toggle('has_content');
  4828. selectTextareaContent('content_audio_playlist_textarea');
  4829. break;
  4830. }
  4831. }
  4832. $('#title, #content_audio_title').on('click',function() {
  4833. if ( $content_pane.attr('data-content') !== 'has_font_file' && $content_pane.attr('data-content') !== 'has_grid' ) { showPlaylistEntry($(this).attr('id')); }
  4834. });
  4835. // Open File
  4836. function openFile(e,type) { // type: font or playlist.
  4837. if (window.File && window.FileReader && window.FileList && window.Blob) {
  4838. let files = e.target.files[0];
  4839. let reader = new FileReader();
  4840. if ( type === 'font' ) { reader.readAsArrayBuffer(files); }
  4841. if ( type === 'playlist' ) { reader.readAsText(files); }
  4842. reader.onload = function() {
  4843. if ( type === 'font' ) { openFontFile(files,reader); }
  4844. if ( type === 'playlist' ) { openPlaylist(files,reader); }
  4845. return true;
  4846. };
  4847. } else {
  4848. alert('Can\'t open file: file APIs are not fully supported in this browser.');
  4849. }
  4850. }
  4851. // Save File
  4852. function saveFile(content,mimetype,file_name) {
  4853. let blob = new Blob([content], {type: mimetype});
  4854. let $download_el = window.document.createElement('a');
  4855. $download_el.style = "display:none";
  4856. $download_el.href = window.URL.createObjectURL(blob);
  4857. $download_el.download = file_name;
  4858. document.body.appendChild($download_el);
  4859. $download_el.click();
  4860. document.body.removeChild($download_el);
  4861. URL.revokeObjectURL(blob);
  4862. }
  4863.  
  4864. })();
  4865. // FINIS! + DEO GRATIAS + //