diff options
Diffstat (limited to 'ext/js/display')
| -rw-r--r-- | ext/js/display/display-content-manager.js | 46 | ||||
| -rw-r--r-- | ext/js/display/display.js | 9 | ||||
| -rw-r--r-- | ext/js/display/sandbox/structured-content-generator.js | 28 | 
3 files changed, 80 insertions, 3 deletions
| diff --git a/ext/js/display/display-content-manager.js b/ext/js/display/display-content-manager.js index 0818f915..0b91b40c 100644 --- a/ext/js/display/display-content-manager.js +++ b/ext/js/display/display-content-manager.js @@ -37,11 +37,14 @@  class DisplayContentManager {      /**       * Creates a new instance of the class. +     * @param {Display} display The display instance that owns this object.       */ -    constructor() { +    constructor(display) { +        this._display = display;          this._token = {};          this._mediaCache = new Map();          this._loadMediaData = []; +        this._eventListeners = new EventListenerCollection();      }      /** @@ -77,6 +80,23 @@ class DisplayContentManager {          this._mediaCache.clear();          this._token = {}; + +        this._eventListeners.removeAllEventListeners(); +    } + +    /** +     * Sets up attributes and events for a link element. +     * @param {Element} element The link element. +     * @param {string} href The URL. +     * @param {boolean} internal Whether or not the URL is an internal or external link. +     */ +    prepareLink(element, href, internal) { +        element.href = href; +        if (!internal) { +            element.target = '_blank'; +            element.rel = 'noreferrer noopener'; +        } +        this._eventListeners.addEventListener(element, 'click', this._onLinkClick.bind(this));      }      async _loadMedia(path, dictionary, onLoad, onUnload) { @@ -127,4 +147,28 @@ class DisplayContentManager {          }          return cachedData;      } + +    _onLinkClick(e) { +        const {href} = e.currentTarget; +        if (typeof href !== 'string') { return; } + +        const baseUrl = new URL(location.href); +        const url = new URL(href, baseUrl); +        const internal = (url.protocol === baseUrl.protocol && url.host === baseUrl.host); +        if (!internal) { return; } + +        e.preventDefault(); + +        const params = {}; +        for (const [key, value] of url.searchParams.entries()) { +            params[key] = value; +        } +        this._display.setContent({ +            historyMode: 'new', +            focus: false, +            params, +            state: null, +            content: null +        }); +    }  } diff --git a/ext/js/display/display.js b/ext/js/display/display.js index a89008b4..02d8513f 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -52,7 +52,7 @@ class Display extends EventDispatcher {          this._styleNode = null;          this._eventListeners = new EventListenerCollection();          this._setContentToken = null; -        this._contentManager = new DisplayContentManager(); +        this._contentManager = new DisplayContentManager(this);          this._hotkeyHelpController = new HotkeyHelpController();          this._displayGenerator = new DisplayGenerator({              japaneseUtil, @@ -938,7 +938,7 @@ class Display extends EventDispatcher {          this._dictionaryEntries = dictionaryEntries; -        this._updateNavigation(this._history.hasPrevious(), this._history.hasNext()); +        this._updateNavigationAuto();          this._setNoContentVisible(dictionaryEntries.length === 0 && lookup);          const container = this._container; @@ -1002,6 +1002,7 @@ class Display extends EventDispatcher {      _clearContent() {          this._container.textContent = ''; +        this._updateNavigationAuto();          this._setQuery('', '', 0);          this._triggerContentUpdateStart(); @@ -1058,6 +1059,10 @@ class Display extends EventDispatcher {          document.title = title;      } +    _updateNavigationAuto() { +        this._updateNavigation(this._history.hasPrevious(), this._history.hasNext()); +    } +      _updateNavigation(previous, next) {          const {documentElement} = document;          if (documentElement !== null) { diff --git a/ext/js/display/sandbox/structured-content-generator.js b/ext/js/display/sandbox/structured-content-generator.js index 799da586..eb847d07 100644 --- a/ext/js/display/sandbox/structured-content-generator.js +++ b/ext/js/display/sandbox/structured-content-generator.js @@ -59,6 +59,8 @@ class StructuredContentGenerator {                  return this._createStructuredContentElement(tag, content, dictionary, 'simple', true, true);              case 'img':                  return this.createDefinitionImage(content, dictionary); +            case 'a': +                return this._createLinkElement(content, dictionary);          }          return null;      } @@ -253,4 +255,30 @@ class StructuredContentGenerator {          if (typeof marginRight === 'number') { style.marginRight = `${marginRight}em`; }          if (typeof marginBottom === 'number') { style.marginBottom = `${marginBottom}em`; }      } + +    _createLinkElement(content, dictionary) { +        let {href} = content; +        const internal = href.startsWith('?'); +        if (internal) { +            href = `${location.protocol}//${location.host}/search.html${href.length > 1 ? href : ''}`; +        } + +        const node = this._createElement('a', 'gloss-link'); +        node.dataset.external = `${!internal}`; + +        const text = this._createElement('span', 'gloss-link-text'); +        node.appendChild(text); + +        const child = this.createStructuredContent(content.content, dictionary); +        if (child !== null) { text.appendChild(child); } + +        if (!internal) { +            const icon = this._createElement('span', 'gloss-link-external-icon icon'); +            icon.dataset.icon = 'external-link'; +            node.appendChild(icon); +        } + +        this._contentManager.prepareLink(node, href, internal); +        return node; +    }  } |