From 91ba44a169b65354b3a82ee5c1ca8e23639766c9 Mon Sep 17 00:00:00 2001 From: Aleksey Solovyev Date: Tue, 29 Nov 2022 14:02:31 +0300 Subject: [PATCH 1/6] refactor: delete copy link button from header --- src/backend/views/pages/blocks/header.twig | 8 +- src/frontend/js/modules/page.js | 39 +------- src/frontend/styles/components/page.pcss | 103 --------------------- 3 files changed, 5 insertions(+), 145 deletions(-) diff --git a/src/backend/views/pages/blocks/header.twig b/src/backend/views/pages/blocks/header.twig index f069652..476d646 100644 --- a/src/backend/views/pages/blocks/header.twig +++ b/src/backend/views/pages/blocks/header.twig @@ -1,11 +1,5 @@ - - - + {{ text }} - diff --git a/src/frontend/js/modules/page.js b/src/frontend/js/modules/page.js index 00bbaf9..148b3e6 100644 --- a/src/frontend/js/modules/page.js +++ b/src/frontend/js/modules/page.js @@ -20,10 +20,7 @@ export default class Page { */ static get CSS() { return { - page: 'page', - copyLinkBtn: 'block-header__copy-link', - header: 'block-header--anchor', - headerLinkCopied: 'block-header--link-copied', + page: 'page' }; } @@ -35,11 +32,11 @@ export default class Page { this.tableOfContent = this.createTableOfContent(); /** - * Add click event listener to capture copy link button clicks + * Add click event listener */ const page = document.querySelector(`.${Page.CSS.page}`); - page.addEventListener('click', this.copyAnchorLinkIfNeeded); + page.addEventListener('click', (event) => { }); } /** @@ -69,39 +66,11 @@ export default class Page { try { // eslint-disable-next-line no-new new TableOfContent({ - tagSelector: - 'h2.block-header--anchor,' + - 'h3.block-header--anchor,' + - 'h4.block-header--anchor', + tagSelector: '.block-header', appendTo: document.getElementById('layout-sidebar-right'), }); } catch (error) { console.error(error); // @todo send to Hawk } } - - /** - * Checks if 'copy link' button was clicked and copies the link to clipboard - * - * @param e - click event - */ - copyAnchorLinkIfNeeded = async (e) => { - const copyLinkButtonClicked = e.target.closest(`.${Page.CSS.copyLinkBtn}`); - - if (!copyLinkButtonClicked) { - return; - } - - const header = e.target.closest(`.${Page.CSS.header}`); - const link = header.querySelector('a').href; - - await copyToClipboard(link); - header.classList.add(Page.CSS.headerLinkCopied); - - header.addEventListener('mouseleave', () => { - setTimeout(() => { - header.classList.remove(Page.CSS.headerLinkCopied); - }, 500); - }, { once: true }); - } } diff --git a/src/frontend/styles/components/page.pcss b/src/frontend/styles/components/page.pcss index 7109556..c359a4c 100644 --- a/src/frontend/styles/components/page.pcss +++ b/src/frontend/styles/components/page.pcss @@ -125,111 +125,9 @@ pointer-events: none; } - &--link-copied { - .block-header__copy-link, - .block-header__copy-link:hover { - background: var(--color-success); - opacity: 1; - animation: check-square-in 250ms ease-in; - pointer-events: none; - } - - .block-header__copy-link-icon--initial { - display: none; - } - - .block-header__copy-link-icon--success { - display: flex; - animation: check-sign-in 350ms ease-in forwards; - } - - .block-header__copy-link-splash { - opacity: 0; - visibility: visible; - transform: scale(3.5); - } - } - - &__copy-link-splash { - position: absolute; - left: 0; - width: 28px; - height: 28px; - background-color: var(--color-success); - transform: scale(1); - border-radius: 100%; - transition: transform 400ms ease-out, opacity 400ms; - visibility: hidden; - } - - &__copy-link-icon--success { - width: 24px; - height: 24px; - display: none; - color: white; - } - - &__copy-link-icon--initial { - display: flex; - justify-content: center; - align-items: center; - } - - &__copy-link { - width: 28px; - height: 28px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - border-radius: 8px; - @apply --squircle; - color: var(--color-text-second); - opacity: 0; - margin-right: 8px; - - @media (--can-hover) { - &:hover { - background: var(--color-link-hover); - } - } - } - - @media (--can-hover) { - &:hover { - .block-header__copy-link { - opacity: 1; - } - } - } - .inline-code { line-height: inherit; } - - @keyframes check-sign-in { - from { - transform: scale(.7); - } - 80% { - transform: scale(1.1); - } - to { - transform: none; - } - } - - @keyframes check-square-in { - from { - transform: scale(1.05); - } - 80% { - transform: scale(.96); - } - to { - transform: none; - } - } } /** @@ -580,4 +478,3 @@ } } } - From 285b94779f25370a2ff02d9854aefbaaf1110029 Mon Sep 17 00:00:00 2001 From: Aleksey Solovyev Date: Tue, 29 Nov 2022 14:07:20 +0300 Subject: [PATCH 2/6] feat: add reusable copy button component --- src/backend/views/components/copy-button.twig | 25 +++++ src/frontend/js/modules/page.js | 39 ++++++- .../styles/components/copy-button.pcss | 103 ++++++++++++++++++ src/frontend/styles/main.pcss | 1 + 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/backend/views/components/copy-button.twig create mode 100644 src/frontend/styles/components/copy-button.pcss diff --git a/src/backend/views/components/copy-button.twig b/src/backend/views/components/copy-button.twig new file mode 100644 index 0000000..2db7ee1 --- /dev/null +++ b/src/backend/views/components/copy-button.twig @@ -0,0 +1,25 @@ +{# + Reusable copy button component. + Available props: + - ariaLabel: label for better accessibility + - class: additional class for the button + - textToCopy: text to be copied to the clipboard (use '#' for anchor links) + + Usage examples: + {% include 'components/copy-button.twig' with { textToCopy: 'Lorem ipsum dolor' } %} + {% include 'components/copy-button.twig' with { textToCopy: '#anchor-link-dolor' } %} +#} + +{% set attrNameForTextToCopy = 'data-text-to-copy' %} + +{% set ariaLabel = ariaLabel ?? 'Copy to the Clipboard' %} + +{% set mainTag = 'button' %} +{% set mainClass = 'copy-button' %} + +<{{ mainTag }} class="{{ mainClass }} {{ class ?? '' }}" aria-label="{{ ariaLabel }}" {{ attrNameForTextToCopy }}="{{ textToCopy }}"> +
+
{{ svg('copy') }}
+
{{ svg('check') }}
+
+ diff --git a/src/frontend/js/modules/page.js b/src/frontend/js/modules/page.js index 148b3e6..126b5fd 100644 --- a/src/frontend/js/modules/page.js +++ b/src/frontend/js/modules/page.js @@ -20,7 +20,9 @@ export default class Page { */ static get CSS() { return { - page: 'page' + copyButton: 'copy-button', + copyButtonCopied: 'copy-button__copied', + page: 'page', }; } @@ -36,7 +38,11 @@ export default class Page { */ const page = document.querySelector(`.${Page.CSS.page}`); - page.addEventListener('click', (event) => { }); + page.addEventListener('click', (event) => { + if (event.target.classList.contains(Page.CSS.copyButton)) { + this.handleCopyButtonClickEvent(event); + } + }); } /** @@ -73,4 +79,33 @@ export default class Page { console.error(error); // @todo send to Hawk } } + + /** + * Handles copy button click events + * + * @param {Event} e - Event Object. + * @returns {Promise} + */ + async handleCopyButtonClickEvent({ target }) { + if (target.classList.contains(Page.CSS.copyButtonCopied)) return; + + let textToCopy = target.getAttribute('data-text-to-copy'); + if (!textToCopy) return; + + // Check if text to copy is an anchor link + if (/^#\S*$/.test(textToCopy)) + textToCopy = window.location.origin + window.location.pathname + textToCopy; + + try { + await copyToClipboard(textToCopy); + + target.classList.add(Page.CSS.copyButtonCopied); + target.addEventListener('mouseleave', () => { + setTimeout(() => target.classList.remove(Page.CSS.copyButtonCopied), 5e2); + }, { once: true }); + + } catch (error) { + console.error(error); // @todo send to Hawk + } + } } diff --git a/src/frontend/styles/components/copy-button.pcss b/src/frontend/styles/components/copy-button.pcss new file mode 100644 index 0000000..2ec2e42 --- /dev/null +++ b/src/frontend/styles/components/copy-button.pcss @@ -0,0 +1,103 @@ +.copy-button { + position: relative; + width: 28px; + height: 28px; + padding: 0; + border: none; + background: none; + cursor: pointer; + transition: opacity 200ms; + + @media (--can-hover) { + &:hover .copy-button__inner { + background: var(--color-link-hover); + } + } + + &::before { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + border-radius: 100%; + background-color: var(--color-success); + visibility: hidden; + pointer-events: none; + transform: scale(1); + transition: transform 400ms ease-out, opacity 400ms; + } + + &__inner { + @apply --squircle; + + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + background: white; + pointer-events: none; + } + + &__icon--initial { + display: flex; + transform: translateZ(0); + } + + &__icon--success { + display: none; + width: 24px; + height: 24px; + color: white; + } + + &__copied { + + &::before { + opacity: 0; + visibility: visible; + transform: scale(3.5); + } + + .copy-button__inner, + .copy-button__inner:hover { + background: var(--color-success) !important; + animation: check-square-in 250ms ease-in; + } + + .copy-button__icon--initial { + display: none; + } + + .copy-button__icon--success { + display: flex; + animation: check-sign-in 350ms ease-in forwards; + } + } + + @keyframes check-sign-in { + from { + transform: scale(.7); + } + 80% { + transform: scale(1.1); + } + to { + transform: none; + } + } + + @keyframes check-square-in { + from { + transform: scale(1.05); + } + 80% { + transform: scale(.96); + } + to { + transform: none; + } + } +} diff --git a/src/frontend/styles/main.pcss b/src/frontend/styles/main.pcss index c436ade..d6edcbf 100644 --- a/src/frontend/styles/main.pcss +++ b/src/frontend/styles/main.pcss @@ -5,6 +5,7 @@ @import './carbon.pcss'; @import './components/header.pcss'; @import './components/writing.pcss'; +@import './components/copy-button.pcss'; @import './components/page.pcss'; @import './components/greeting.pcss'; @import './components/auth.pcss'; From dc0c32a93f97b35cf0e8e9bc8a927a4bf7ab8b68 Mon Sep 17 00:00:00 2001 From: Aleksey Solovyev Date: Tue, 29 Nov 2022 14:08:37 +0300 Subject: [PATCH 3/6] feat: add copy button to header --- src/backend/views/pages/blocks/header.twig | 7 +++++++ src/frontend/styles/components/page.pcss | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/backend/views/pages/blocks/header.twig b/src/backend/views/pages/blocks/header.twig index 476d646..e2e0403 100644 --- a/src/backend/views/pages/blocks/header.twig +++ b/src/backend/views/pages/blocks/header.twig @@ -1,4 +1,11 @@ + {% + include 'components/copy-button.twig' with { + ariaLabel: 'Copy Link to the ' ~ text, + class: 'block-header__copy-button', + textToCopy: '#' ~ text | urlify, + } + %} {{ text }} diff --git a/src/frontend/styles/components/page.pcss b/src/frontend/styles/components/page.pcss index c359a4c..c3846a1 100644 --- a/src/frontend/styles/components/page.pcss +++ b/src/frontend/styles/components/page.pcss @@ -125,6 +125,20 @@ pointer-events: none; } + &__copy-button { + margin-right: 8px; + color: var(--color-text-second); + opacity: 0; + } + + @media (--can-hover) { + &:hover { + .block-header__copy-button { + opacity: 1; + } + } + } + .inline-code { line-height: inherit; } From 1c3ada993e77353295716acc5b9d30c559dd9fc2 Mon Sep 17 00:00:00 2001 From: Aleksey Solovyev Date: Tue, 29 Nov 2022 14:09:55 +0300 Subject: [PATCH 4/6] feat: add copy button to code block --- src/backend/views/pages/blocks/code.twig | 12 ++++++++++-- src/frontend/styles/components/page.pcss | 21 +++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/backend/views/pages/blocks/code.twig b/src/backend/views/pages/blocks/code.twig index 663ff49..2402cb5 100644 --- a/src/backend/views/pages/blocks/code.twig +++ b/src/backend/views/pages/blocks/code.twig @@ -1,4 +1,12 @@
-
{{ code|escape }}
+
+
{{ code | escape }}
+
+ {% + include 'components/copy-button.twig' with { + ariaLabel: 'Copy Code to Clipboard', + class: 'block-code__copy-button', + textToCopy: code | escape, + } + %}
- diff --git a/src/frontend/styles/components/page.pcss b/src/frontend/styles/components/page.pcss index c3846a1..20a408d 100644 --- a/src/frontend/styles/components/page.pcss +++ b/src/frontend/styles/components/page.pcss @@ -149,8 +149,18 @@ * ================== */ .block-code { - @apply --text-code-block; - @apply --squircle; + position: relative; + + &:hover { + .block-code__copy-button { + opacity: 1; + } + } + + &__wrapper { + @apply --text-code-block; + @apply --squircle; + } &__content { display: inline-block !important; @@ -169,6 +179,13 @@ } } + &__copy-button { + position: absolute; + top: 10px; + right: 10px; + opacity: 0; + } + .hljs-params { color: var(--color-code-params); } From 3ca4a175e9ee06196a9d35d43b91dfffb757dd67 Mon Sep 17 00:00:00 2001 From: Aleksey Solovyev Date: Tue, 29 Nov 2022 14:54:11 +0300 Subject: [PATCH 5/6] fix: trim content of TOC elements This is necessary in order to avoid creating
elements in the table of contens elements. --- src/frontend/js/classes/table-of-content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/js/classes/table-of-content.js b/src/frontend/js/classes/table-of-content.js index b4900fb..9e46e94 100644 --- a/src/frontend/js/classes/table-of-content.js +++ b/src/frontend/js/classes/table-of-content.js @@ -193,7 +193,7 @@ export default class TableOfContent { const linkWrapper = $.make('li', this.CSS.tocElementItem); const linkBlock = $.make('a', null, { - innerText: tag.innerText, + innerText: tag.innerText.trim(), href: `${linkTarget}`, }); From 36ba3a3cd8628ee9213eb00006ea0d26b795a98d Mon Sep 17 00:00:00 2001 From: Aleksey Solovyev Date: Tue, 29 Nov 2022 22:01:50 +0300 Subject: [PATCH 6/6] fix: prevent scrollbar-x from appearing on small screens --- src/frontend/styles/layout.pcss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/styles/layout.pcss b/src/frontend/styles/layout.pcss index 8f6e899..0680987 100644 --- a/src/frontend/styles/layout.pcss +++ b/src/frontend/styles/layout.pcss @@ -8,6 +8,7 @@ .docs { min-height: calc(100vh - var(--layout-height-header)); + overflow-x: hidden; @media (--desktop) { display: flex;