diff --git a/assets/javascripts/templates/notice_tmpl.js b/assets/javascripts/templates/notice_tmpl.js index 49793eb571..26c8c947b1 100644 --- a/assets/javascripts/templates/notice_tmpl.js +++ b/assets/javascripts/templates/notice_tmpl.js @@ -7,3 +7,9 @@ app.templates.singleDocNotice = (doc) => app.templates.disabledDocNotice = () => notice(` This documentation is disabled. To enable it, go to Preferences. `); + +app.templates.noOriginalLinkNotice = () => + notice(` The original page link is not available for this documentation. `); + +app.templates.copyFailedNotice = () => + notice(` Couldn't copy the original page link to the clipboard. `); diff --git a/assets/javascripts/views/content/entry_page.js b/assets/javascripts/views/content/entry_page.js index 32935b4172..2fc24d4b5d 100644 --- a/assets/javascripts/views/content/entry_page.js +++ b/assets/javascripts/views/content/entry_page.js @@ -23,6 +23,7 @@ app.views.EntryPage = class EntryPage extends app.View { deactivate() { if (super.deactivate(...arguments)) { + this.hideTransientNotice(); this.empty(); this.entry = null; } @@ -218,20 +219,58 @@ app.views.EntryPage = class EntryPage extends app.View { } } + originalLink() { + // The attribution is appended last but may be followed by other elements, + // so match on the last attribution rather than on its sibling position. + const links = this.findAll("._attribution ._attribution-link"); + return links[links.length - 1]; + } + onAltC() { - const link = this.find("._attribution:last-child ._attribution-link"); + const link = this.originalLink(); if (!link) { + this.showTransientNotice("noOriginalLink"); + return; + } + if (!navigator.clipboard) { + this.showTransientNotice("copyFailed"); return; } - console.log(link.href + location.hash); - navigator.clipboard.writeText(link.href + location.hash); + navigator.clipboard.writeText(link.href + location.hash).catch(() => { + // The rejection may arrive after the user navigated away. This view is + // reused across entries, so only report the failure while the page that + // was copied from is still the one on screen. + if (this.activated && link.isConnected) { + this.showTransientNotice("copyFailed"); + } + }); } onAltO() { - const link = this.find("._attribution:last-child ._attribution-link"); + const link = this.originalLink(); if (!link) { + this.showTransientNotice("noOriginalLink"); return; } this.delay(() => $.popup(link.href + location.hash)); } + + showTransientNotice(type) { + this.hideTransientNotice(); + this.transientNotice = new app.views.Notice(type); + // Persistent notices (single doc, disabled doc) share the same bounds and + // z-index, so raise this one to keep it visible while it's shown. + this.transientNotice.addClass("_notice-transient"); + this.transientNoticeTimer = this.delay(this.hideTransientNotice, 3000); + } + + hideTransientNotice() { + if (!this.transientNotice) { + return; + } + clearTimeout(this.transientNoticeTimer); + this.transientNotice.deactivate(); + this.transientNotice = null; + this.transientNoticeTimer = null; + } }; diff --git a/assets/stylesheets/components/_notice.scss b/assets/stylesheets/components/_notice.scss index 497a8989b4..9ccbb039a2 100644 --- a/assets/stylesheets/components/_notice.scss +++ b/assets/stylesheets/components/_notice.scss @@ -18,6 +18,8 @@ ~ ._container { padding-bottom: 2.5rem; } } +._notice-transient { z-index: calc(var(--noticeZ) + 1); } + ._notice-text { display: table-cell; vertical-align: middle;