aboutsummaryrefslogtreecommitdiff
path: root/ext/js/display
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js/display')
-rw-r--r--ext/js/display/display-content-manager.js46
-rw-r--r--ext/js/display/display.js9
-rw-r--r--ext/js/display/sandbox/structured-content-generator.js28
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;
+ }
}