Parcourir la source

Remove `mimeType` parts being returned by `Entry.type`.
Add `Entry.type` as a `data-` attribute to `Item`s.
Add navigation functionality to preview overlay. Addresses #60.
Add `trailingSlash` lib function.

dom111 il y a 5 ans
Parent
commit
95b78c9e1e

+ 2 - 3
src/lib/DAV.js

@@ -2,6 +2,7 @@ import EventObject from './EventObject.js';
 import HTTP from './HTTP.js';
 import Response from './DAV/Response.js';
 import joinPath from './joinPath.js';
+import {trailingSlash} from './trailingSlash.js';
 
 export default class DAV extends EventObject {
   #bypassCheck;
@@ -96,9 +97,7 @@ export default class DAV extends EventObject {
   }
 
   async list(uri, bypassCache) {
-    if (! uri.match(/\/$/)) {
-      uri = `${uri}/`;
-    }
+    uri = trailingSlash(uri);
 
     if (! bypassCache) {
       const cached = await this.#cache.get(uri);

+ 1 - 7
src/lib/DAV/Entry.js

@@ -163,15 +163,13 @@ export default class Entry extends EventObject {
 
   get type() {
     if (! this.#type) {
-      let type;
-
       const types = {
         text: /\.(?:te?xt|i?nfo|php|cgi|faq|ini|htaccess|log|md|sql|sfv|conf|sh|pl|pm|py|rb|(?:s?c|sa)ss|js|java|coffee|[sx]?html?|xml)$/i,
         image: /\.(?:jpe?g|gif|a?png|svg)$/i,
         video: /\.(?:mp(?:e?g)?4|mov|avi|webm|ogv)$/i,
         audio: /\.(?:mp3|wav|ogg)$/i,
         font: /\.(?:woff2?|eot|[ot]tf)$/i,
-        pdf: /\.pdf/i
+        pdf: /\.pdf$/i
       };
 
       for (const [key, value] of Object.entries(types)) {
@@ -180,10 +178,6 @@ export default class Entry extends EventObject {
         }
       }
 
-      if (this.#mimeType && (type = this.#mimeType.split('/').shift())) {
-        return this.#type = type;
-      }
-
       this.#type = 'unknown';
     }
 

+ 69 - 13
src/lib/UI/NativeDOM.js

@@ -2,6 +2,7 @@ import Container from './NativeDOM/Container.js';
 import Footer from './NativeDOM/Footer.js';
 import Melba from 'melba-toast';
 import UI from './UI.js';
+import {trailingSlash} from '../trailingSlash.js';
 
 export default class NativeDOM extends UI {
   render(container = new Container(), footer = new Footer()) {
@@ -22,7 +23,17 @@ export default class NativeDOM extends UI {
         return typeof element[`on${eventName}`] === 'function';
       },
       isTouch = supportsEvent('touchstart'),
-      supportsDragDrop = supportsEvent('dragstart') && supportsEvent('drop')
+      supportsDragDrop = supportsEvent('dragstart') && supportsEvent('drop'),
+      updateTitle = (title) => {
+        if (document.title !== title) {
+          document.title = title;
+        }
+      },
+      updatePath = (path) => {
+        if (location.pathname !== path) {
+          history.pushState(history.state, path, path);
+        }
+      }
     ;
 
     // DOM events
@@ -37,16 +48,26 @@ export default class NativeDOM extends UI {
     window.addEventListener('popstate', () => {
       const url = location.pathname;
 
+      element.dispatchEvent(new CustomEvent('preview:close', {
+        bubbles: true,
+        detail: {
+          preview: true,
+        }
+      }));
+
       if (url.endsWith('/')) {
         return this.trigger('go');
       }
 
-      const parts = url.split(/\//),
-        // file = parts.pop(),
-        path = parts.join('/')
-      ;
+      const path = url.replace(/[^/]+$/, '');
 
-      this.trigger('go', path);
+      this.trigger('go', path, {
+        bypassPushState: true,
+        success: () => this.container
+          .querySelector(`main ul li[data-full-path="${url}"]`)
+          ?.dispatchEvent(new CustomEvent('click'))
+        ,
+      });
 
       // trigger opening file
     });
@@ -183,8 +204,16 @@ export default class NativeDOM extends UI {
       });
     });
 
-    this.on('go', async (path = location.pathname, bypassCache = false, failure = null) => {
-      const prevPath = location.pathname;
+    this.on('go', async (
+      path = location.pathname,
+      {
+        bypassCache = false,
+        bypassPushState = false,
+        failure = null,
+        success = null,
+      } = {}
+    ) => {
+      path = trailingSlash(path);
 
       this.trigger('list:update:request', path);
 
@@ -203,14 +232,41 @@ export default class NativeDOM extends UI {
 
       this.trigger('list:update:success', collection);
 
-      if (path !== prevPath) {
-        history.pushState(history.state, path, path);
+      if (! bypassPushState) {
+        updatePath(path);
+      }
+
+      updateTitle(`${decodeURIComponent(path)} | WebDAV`);
+
+      if (success) {
+        success(collection);
       }
+    });
 
-      document.title = `${decodeURIComponent(path)} | WebDAV`;
+    this.on('preview:opened', (entry) => {
+      document.body.classList.add('preview-open');
+      this.container
+        .querySelector(`[data-full-path="${entry.fullPath}"]`)
+        ?.focus()
+      ;
+
+      updatePath(entry.fullPath);
+      updateTitle(`${decodeURIComponent(entry.fullPath)} | WebDAV`);
     });
 
-    this.on('preview:opened', (entry) => history.pushState(history.state, entry.fullPath, entry.fullPath));
-    this.on('preview:closed', (entry) => history.pushState(history.state, entry.path, entry.path));
+    this.on('preview:closed', (entry, {
+      preview = false,
+    } = {}) => {
+      if (preview) {
+        return;
+      }
+
+      const path = trailingSlash(entry.path);
+
+      document.body.classList.remove('preview-open');
+
+      updatePath(path);
+      updateTitle(`${decodeURIComponent(path)} | WebDAV`);
+    });
   }
 }

+ 2 - 1
src/lib/UI/NativeDOM/Footer.js

@@ -1,5 +1,6 @@
 import Element from './Element.js';
 import joinPath from '../../joinPath.js';
+import {trailingSlash} from '../../trailingSlash.js';
 
 export default class Footer extends Element {
   constructor() {
@@ -33,7 +34,7 @@ export default class Footer extends Element {
         return;
       }
 
-      this.trigger('create-directory', `${joinPath(location.pathname, directoryName)}/`, directoryName, location.pathname);
+      this.trigger('create-directory', trailingSlash(joinPath(location.pathname, directoryName)), directoryName, location.pathname);
     });
   }
 }

+ 47 - 2
src/lib/UI/NativeDOM/List.js

@@ -40,15 +40,60 @@ export default class List extends Element {
       event.stopPropagation();
 
       const current = this.element.querySelector(`li:focus${supportsFocusWithin ? ', li:focus-within' : ''}`),
-        next = current ? current.nextSibling : this.element.querySelector('li:first-child'),
-        previous = current ? current.previousSibling : null
+        isPreview = document.body.classList.contains('preview-open'),
+        previewItems = [...this.element.querySelectorAll('li:not(.directory):not([data-type="unknown"])')],
+        currentItemIndex = previewItems.indexOf(current),
+        next = (isPreview) ?
+          (currentItemIndex > -1) ?
+            previewItems.slice(
+              currentItemIndex + 1
+            )
+              .shift() :
+            null :
+          current ?
+            current.nextElementSibling :
+            this.element.querySelector('li:first-child')
+        ,
+        previous = (isPreview) ?
+          (currentItemIndex > -1) ?
+            previewItems.slice(
+              0,
+              currentItemIndex
+            )
+              .pop() :
+            null :
+          current ?
+            current.previousElementSibling :
+            null
       ;
 
       if (event.which === 38 && previous) { // if (event.key === 'ArrowUp' && previous) {
         previous.focus();
+
+        if (isPreview) {
+          this.element.dispatchEvent(new CustomEvent('preview:close', {
+            bubbles: true,
+            detail: {
+              preview: true
+            }
+          }));
+
+          previous.dispatchEvent(new CustomEvent('click'));
+        }
       }
       else if (event.which === 40 && next) { // else if (event.key === 'ArrowDown' && next) {
         next.focus();
+
+        if (isPreview) {
+          this.element.dispatchEvent(new CustomEvent('preview:close', {
+            bubbles: true,
+            detail: {
+              preview: true
+            }
+          }));
+
+          next.dispatchEvent(new CustomEvent('click'));
+        }
       }
     };
 

+ 18 - 5
src/lib/UI/NativeDOM/List/Item.js

@@ -34,7 +34,7 @@ export default class Item extends Element {
   });
 
   constructor(entry, base64Encoder = btoa) {
-    const template = `<li tabindex="0" data-full-path=${entry.fullPath}">
+    const template = `<li tabindex="0" data-full-path="${entry.fullPath}" data-type="${entry.type}">
   <span class="title">${entry.title}</span>
   <input type="text" name="rename" class="hidden" readonly>
   <span class="size">${entry.displaySize}</span>
@@ -156,7 +156,9 @@ export default class Item extends Element {
       return;
     }
 
-    this.element.querySelector('[download]').click();
+    this.element.querySelector('[download]')
+      .dispatchEvent(new CustomEvent('click'))
+    ;
   }
 
   loading(loading = true) {
@@ -179,16 +181,23 @@ export default class Item extends Element {
     }
 
     const launchLightbox = (lightboxContent, onShow) => {
-      const escapeListener = (event) => {
+      const close = () => lightbox.close(),
+        escapeListener = (event) => {
           if (event.which === 27) { // if (event.key === 'Escape') {
-            lightbox.close();
+            close();
           }
         },
         lightbox = BasicLightbox.create(lightboxContent, {
           className: entry.type,
           onShow: () => {
             this.loading(false);
+
             document.addEventListener('keydown', escapeListener);
+            document.addEventListener('preview:close', (event) => {
+              lightbox.preview = event.detail?.preview;
+
+              close();
+            });
 
             if (onShow) {
               onShow(lightbox);
@@ -196,7 +205,11 @@ export default class Item extends Element {
           },
           onClose: () => {
             document.removeEventListener('keydown', escapeListener);
-            this.trigger('preview:closed', this.#entry);
+            document.removeEventListener('preview:close', close);
+
+            this.trigger('preview:closed', this.#entry, {
+              preview: lightbox.preview,
+            });
           }
         })
       ;

+ 1 - 0
src/lib/trailingSlash.js

@@ -0,0 +1 @@
+export const trailingSlash = (string) => string.endsWith('/') ? string : `${string}/`;

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
src/webdav-min.js


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff