diff --git a/app/javascript/packs/portal.js b/app/javascript/packs/portal.js index 5c1867658..42493615d 100644 --- a/app/javascript/packs/portal.js +++ b/app/javascript/packs/portal.js @@ -1,35 +1,9 @@ -// This file is automatically compiled by Webpack, along with any other files -// present in this directory. You're encouraged to place your actual application logic in -// a relevant structure within app/javascript and only use these pack files to reference -// that code so that it will be compiled. - -import Vue from 'vue'; import Rails from '@rails/ujs'; import Turbolinks from 'turbolinks'; -import PublicArticleSearch from '../portal/components/PublicArticleSearch.vue'; - -import { navigateToLocalePage } from '../portal/portalHelpers'; - import '../portal/application.scss'; +import { InitializationHelpers } from '../portal/portalHelpers'; Rails.start(); Turbolinks.start(); -const initPageSetUp = () => { - navigateToLocalePage(); - const isSearchContainerAvailable = document.querySelector('#search-wrap'); - if (isSearchContainerAvailable) { - new Vue({ - components: { PublicArticleSearch }, - template: '', - }).$mount('#search-wrap'); - } -}; - -document.addEventListener('DOMContentLoaded', () => { - initPageSetUp(); -}); - -document.addEventListener('turbolinks:load', () => { - initPageSetUp(); -}); +document.addEventListener('turbolinks:load', InitializationHelpers.onLoad); diff --git a/app/javascript/portal/application.scss b/app/javascript/portal/application.scss index b657e2249..8a1b7461b 100644 --- a/app/javascript/portal/application.scss +++ b/app/javascript/portal/application.scss @@ -16,3 +16,21 @@ body { -webkit-font-smoothing: antialiased; height: 100%; } + + +// Taking these utils from tailwind 3.x.x, need to remove once we upgrade +.scroll-mt-24 { + scroll-margin-top: 6rem; +} + +.top-24 { + top: 6rem; +} + +.heading { + &:hover { + a { + visibility: visible; + } + } +} diff --git a/app/javascript/portal/components/TableOfContents.vue b/app/javascript/portal/components/TableOfContents.vue new file mode 100644 index 000000000..5e00f50aa --- /dev/null +++ b/app/javascript/portal/components/TableOfContents.vue @@ -0,0 +1,70 @@ + + + + + + {{ tocHeader }} + + + + + + {{ element.title }} + + + + + + + + + diff --git a/app/javascript/portal/portalHelpers.js b/app/javascript/portal/portalHelpers.js index 647059b73..27adad3d5 100644 --- a/app/javascript/portal/portalHelpers.js +++ b/app/javascript/portal/portalHelpers.js @@ -1,13 +1,79 @@ -export const navigateToLocalePage = () => { - const allLocaleSwitcher = document.querySelector('.locale-switcher'); +import slugifyWithCounter from '@sindresorhus/slugify'; +import Vue from 'vue'; - if (!allLocaleSwitcher) { - return false; - } +import PublicArticleSearch from './components/PublicArticleSearch.vue'; +import TableOfContents from './components/TableOfContents.vue'; - const { portalSlug } = allLocaleSwitcher.dataset; - allLocaleSwitcher.addEventListener('change', event => { - window.location = `/hc/${portalSlug}/${event.target.value}/`; +export const getHeadingsfromTheArticle = () => { + const rows = []; + const articleElement = document.getElementById('cw-article-content'); + articleElement.querySelectorAll('h1, h2, h3').forEach(element => { + const slug = slugifyWithCounter(element.innerText); + element.id = slug; + element.className = 'scroll-mt-24 heading'; + element.innerHTML += `#`; + rows.push({ + slug, + title: element.innerText, + tag: element.tagName.toLowerCase(), + }); }); - return false; + return rows; +}; + +export const InitializationHelpers = { + navigateToLocalePage: () => { + const allLocaleSwitcher = document.querySelector('.locale-switcher'); + + if (!allLocaleSwitcher) { + return false; + } + + const { portalSlug } = allLocaleSwitcher.dataset; + allLocaleSwitcher.addEventListener('change', event => { + window.location = `/hc/${portalSlug}/${event.target.value}/`; + }); + return false; + }, + + initalizeSearch: () => { + const isSearchContainerAvailable = document.querySelector('#search-wrap'); + if (isSearchContainerAvailable) { + new Vue({ + components: { PublicArticleSearch }, + template: '', + }).$mount('#search-wrap'); + } + }, + + initializeTableOfContents: () => { + const isOnArticlePage = document.querySelector('#cw-hc-toc'); + if (isOnArticlePage) { + new Vue({ + components: { TableOfContents }, + data: { rows: getHeadingsfromTheArticle() }, + template: '', + }).$mount('#cw-hc-toc'); + } + }, + + initialize: () => { + InitializationHelpers.navigateToLocalePage(); + InitializationHelpers.initalizeSearch(); + InitializationHelpers.initializeTableOfContents(); + }, + + onLoad: () => { + InitializationHelpers.initialize(); + if (window.location.hash) { + if ('scrollRestoration' in window.history) { + window.history.scrollRestoration = 'manual'; + } + + const a = document.createElement('a'); + a.href = window.location.hash; + a['data-turbolinks'] = false; + a.click(); + } + }, }; diff --git a/app/javascript/portal/specs/portal.spec.js b/app/javascript/portal/specs/portal.spec.js index 31dc06890..cd4347bad 100644 --- a/app/javascript/portal/specs/portal.spec.js +++ b/app/javascript/portal/specs/portal.spec.js @@ -1,4 +1,4 @@ -import { navigateToLocalePage } from '../portalHelpers'; +import { InitializationHelpers } from '../portalHelpers'; describe('#navigateToLocalePage', () => { it('returns correct cookie name', () => { @@ -14,7 +14,7 @@ describe('#navigateToLocalePage', () => { callback({ target: { value: 1 } }); }); - navigateToLocalePage(); + InitializationHelpers.navigateToLocalePage(); expect(allLocaleSwitcher.addEventListener).toBeCalledWith( 'change', expect.any(Function) diff --git a/app/views/layouts/portal.html.erb b/app/views/layouts/portal.html.erb index 071fde9ee..8f1dda7ae 100644 --- a/app/views/layouts/portal.html.erb +++ b/app/views/layouts/portal.html.erb @@ -44,8 +44,9 @@ By default, it renders: searchPlaceholder: '<%= I18n.t('public_portal.search.search_placeholder') %>', emptyPlaceholder: '<%= I18n.t('public_portal.search.empty_placeholder') %>', loadingPlaceholder: '<%= I18n.t('public_portal.search.loading_placeholder') %>', - resultsTitle: '<%= I18n.t('public_portal.search.results_title') %>' - } + resultsTitle: '<%= I18n.t('public_portal.search.results_title') %>', + }, + tocHeader: '<%= I18n.t('public_portal.toc_header') %>' };
+ + {{ element.title }} + +