Skip to content

Commit

Permalink
Merge pull request #757 from City-of-Helsinki/UHF-9739
Browse files Browse the repository at this point in the history
UHF-9739 Updating news
  • Loading branch information
khalima authored May 20, 2024
2 parents 9f712ac + d22251e commit 20d94f8
Showing 1 changed file with 167 additions and 122 deletions.
289 changes: 167 additions & 122 deletions modules/helfi_toc/assets/js/tableOfContents.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,71 @@
'use strict';

(function (Drupal, once, drupalSettings) {
Drupal.behaviors.table_of_contents = {
attach: function attach() {

function findAvailableId(name, reserved, anchors, count) {
let newName = name;
if (count > 0) { // Only when headings are not unique on page we want to add counter
newName += `-${count}`;
}
if (reserved.includes(newName)) {
return findAvailableId(name, reserved, anchors, count + 1);
}

if (anchors.includes(newName)) {
if (count === 0) {
count += 1; // When reserved heading is visible on page, lets start counting from 2 instead of 1
}
return findAvailableId(name, reserved, anchors, count + 1);
}
return newName;
}

const anchors = [];
const tableOfContents = document.getElementById('helfi-toc-table-of-contents');
const tableOfContentsList = document.querySelector('#helfi-toc-table-of-contents-list > ul');
const mainContent = document.querySelector('main.layout-main-wrapper');
const reservedElems = document.querySelectorAll('[id]');
const reserved = []; // Let's list current id's here to avoid creating duplicates
reservedElems.forEach(function (elem) {
reserved.push(elem.id);
});
((Drupal, once, drupalSettings) => {

// Global table of contents object.
Drupal.tableOfContents = {

// List of reserved ids.
reservedIds: [],

// List of anchors.
anchors: [],

// Exclude elements from TOC that are not content:
// e.g. TOC, sidebar, cookie compliance banner etc.
exclusions: () => {
return '' +
':not(.layout-sidebar-first *)' +
':not(.layout-sidebar-second *)' +
':not(.tools__container *)' +
':not(.breadcrumb__container *)' +
':not(#helfi-toc-table-of-contents *)' +
':not(.embedded-content-cookie-compliance *)' +
':not(.react-and-share-cookie-compliance *)'
},

// Exclude elements from TOC that are not content:
// e.g. TOC, sidebar, cookie compliency-banner etc.
const exclusions =
'' +
':not(.layout-sidebar-first *)' +
':not(.layout-sidebar-second *)' +
':not(.tools__container *)' +
':not(.breadcrumb__container *)' +
':not(#helfi-toc-table-of-contents *)' +
':not(.embedded-content-cookie-compliance *)' +
':not(.react-and-share-cookie-compliance *)';

const titleComponents = [
// List of heading tags with exclusions.
titleComponents: (exclusions = Drupal.tableOfContents.exclusions()) => {
return [
`h2${exclusions}`,
`h3${exclusions}`,
`h4${exclusions}`,
`h5${exclusions}`,
`h6${exclusions}`,
];
},

const mainLanguages = [
'en',
'fi',
'sv',
];
// Find available ID for the anchor link.
findAvailableId: (name, count) => {
let newName = name;

// Add postfix to the name if heading is not unique.
if (count > 0) {
newName += `-${count}`;
}

if (Drupal.tableOfContents.reservedIds.includes(newName)) {
return Drupal.tableOfContents.findAvailableId(name, count + 1);
}

const swaps = {
if (Drupal.tableOfContents.anchors.includes(newName)) {
// When reserved heading is visible on page, lets start counting from 2 instead of 1
if (count === 0) {
count += 1;
}
return Drupal.tableOfContents.findAvailableId(name,count + 1);
}
return newName;
},

// Main languages.
mainLanguages: () => {
return ['en', 'fi', 'sv'];
},

// Locale conversions.
localeConversions: () => {
return {
'0': '[°₀۰0]',
'1': '[¹₁۱1]',
'2': '[²₂۲2]',
Expand All @@ -70,101 +77,148 @@
'8': '[⁸₈۸8]',
'9': '[⁹₉۹9]',
'a': '[àáảãạăắằẳẵặâấầẩẫậāąåαάἀἁἂἃἄἅἆἇᾀᾁᾂᾃᾄᾅᾆᾇὰᾰᾱᾲᾳᾴᾶᾷаأအာါǻǎªაअاaä]',
'aa': '[عआآ]',
'ae': '[æǽ]',
'ai': '[ऐ]',
'b': '[бβبဗბbब]',
'c': '[çćčĉċc©]',
'ch': '[чჩჭچ]',
'd': '[ďðđƌȡɖɗᵭᶁᶑдδدضဍဒდdᴅᴆ]',
'dj': '[ђđ]',
'dz': '[џძ]',
'e': '[éèẻẽẹêếềểễệëēęěĕėεέἐἑἒἓἔἕὲеёэєəဧေဲეएإئe]',
'ei': '[ऍ]',
'f': '[фφفƒფf]',
'g': '[ĝğġģгґγဂგگg]',
'gh': '[غღ]',
'gx': '[ĝ]',
'h': '[ĥħηήحهဟှჰh]',
'hx': '[ĥ]',
'i': '[íìỉĩịîïīĭįıιίϊΐἰἱἲἳἴἵἶἷὶῐῑῒῖῗіїиဣိီည်ǐიइیii̇ϒ]',
'ii': '[ई]',
'ij': '[ij]',
'j': '[ĵјჯجj]',
'jx': '[ĵ]',
'k': '[ķĸкκقكကკქکk]',
'kh': '[хخხ]',
'l': '[łľĺļŀлλلလლlल]',
'lj': '[љ]',
'm': '[мμمမმm]',
'n': '[ñńňņʼnŋνнنနნn]',
'o': '[óòỏõọôốồổỗộơớờởỡợøōőŏοὀὁὂὃὄὅὸόоوθိုǒǿºოओoöө]',
'p': '[пπပპپp]',
'q': '[ყq]',
'r': '[ŕřŗрρرრr]',
's': '[śšşсσșςسصစſსsŝ]',
't': '[ťţтτțتطဋတŧთტt]',
'u': '[úùủũụưứừửữựûūůűŭųµуဉုူǔǖǘǚǜუउuўü]',
'v': '[вვϐv]',
'w': '[ŵωώဝွw]',
'x': '[χξx]',
'y': '[ýỳỷỹỵÿŷйыυϋύΰيယyῠῡὺ]',
'z': '[źžżзζزဇზz]',
'aa': '[عआآ]',
'ae': '[æǽ]',
'ai': '[ऐ]',
'ch': '[чჩჭچ]',
'dj': '[ђđ]',
'dz': '[џძ]',
'ei': '[ऍ]',
'gh': '[غღ]',
'ii': '[ई]',
'ij': '[ij]',
'kh': '[хخხ]',
'lj': '[љ]',
'nj': '[њ]',
'o': '[óòỏõọôốồổỗộơớờởỡợøōőŏοὀὁὂὃὄὅὸόоوθိုǒǿºოओoöө]',
'oe': '[öœؤ]',
'oi': '[ऑ]',
'oii': '[ऒ]',
'p': '[пπပპپp]',
'ps': '[ψ]',
'q': '[ყq]',
'r': '[ŕřŗрρرრr]',
's': '[śšşсσșςسصစſსsŝ]',
'sh': '[шშش]',
'shch': '[щ]',
'ss': '[ß]',
'sx': '[ŝ]',
't': '[ťţтτțتطဋတŧთტt]',
'th': '[þϑثذظ]',
'ts': '[цცწ]',
'u': '[úùủũụưứừửữựûūůűŭųµуဉုူǔǖǘǚǜუउuўü]',
'ue': '[ü]',
'uu': '[ऊ]',
'v': '[вვϐv]',
'w': '[ŵωώဝွw]',
'x': '[χξx]',
'y': '[ýỳỷỹỵÿŷйыυϋύΰيယyῠῡὺ]',
'ya': '[я]',
'yu': '[ю]',
'z': '[źžżзζزဇზz]',
'zh': '[жჟژ]',
'gx': '[ĝ]',
'hx': '[ĥ]',
'jx': '[ĵ]',
};
},

// Craft table of contents.
once('table-of-contents', titleComponents.join(','), mainContent)
.forEach(function (content) {
let name = content.textContent
.toLowerCase()
.trim();

// To ensure backwards compatibility, this is done only to "other" languages.
if (!mainLanguages.includes(drupalSettings.path.currentLanguage)) {
Object.keys(swaps).forEach((swap) => {
name = name.replace(new RegExp(swaps[swap], 'g'), swap);
});
}
else {
name = name
.replace(/ä/gi, 'a')
.replace(/ö/gi, 'o')
.replace(/å/gi, 'a');
}
// A function to create table of content elements.
createTableOfContentElements: (content) => {
// Remove loading text and noscript element.
let name = content.textContent
.toLowerCase()
.trim();

// To ensure backwards compatibility, this is done only to "other" languages.
if (!Drupal.tableOfContents.mainLanguages().includes(drupalSettings.path.currentLanguage)) {
Object.keys(Drupal.tableOfContents.localeConversions()).forEach((swap) => {
name = name.replace(new RegExp(Drupal.tableOfContents.localeConversions()[swap], 'g'), swap);
});
}
else {
name = name
.replace(/ä/gi, 'a')
.replace(/ö/gi, 'o')
.replace(/å/gi, 'a');
}

name = name
// Replace any remaining non-word character including whitespace with '-'.
// This leaves only characters matching [A-Za-z0-9-_] to the name.
.replace(/\W/g, '-')
// Use underscore at the end of the string: 'example-1' -> 'example_1'.
.replace(/-(\d+)$/g, '_$1');
name = name
// Replace any remaining non-word character including whitespace with '-'.
// This leaves only characters matching [A-Za-z0-9-_] to the name.
.replace(/\W/g, '-')
// Use underscore at the end of the string: 'example-1' -> 'example_1'.
.replace(/-(\d+)$/g, '_$1');

let nodeName = content.nodeName.toLowerCase();
if (nodeName === 'button') {
nodeName = content.parentElement.nodeName.toLowerCase();
}
let nodeName = content.nodeName.toLowerCase();
if (nodeName === 'button') {
nodeName = content.parentElement.nodeName.toLowerCase();
}

const anchorName = content.id
? content.id
: Drupal.tableOfContents.findAvailableId(name, 0);

const anchorName = content.id
? content.id
: findAvailableId(name, reserved, anchors, 0);
Drupal.tableOfContents.anchors.push(anchorName);

anchors.push(anchorName);
// Create anchor links.
content.setAttribute('id', anchorName);
content.setAttribute('tabindex', '-1'); // Set tabindex to -1 to avoid issues with screen readers.

return {
nodeName: nodeName,
anchorName: anchorName,
}
},

// A function to reveal table of contents.
updateTOC: (tocElement) => {
// Remove loading text and noscript element.
const removeElements = tocElement.parentElement.querySelectorAll('.js-remove');
removeElements.forEach(function (element) {
element.remove();
});

// Update toc visible.
tocElement.setAttribute('data-js', 'true');
},
}

// Attach table of contents.
Drupal.behaviors.tableOfContents = {
attach: function attach() {
const tableOfContents = document.getElementById('helfi-toc-table-of-contents');

// Bail if table of contents is not enabled.
if (!tableOfContents) {
return;
}

const tableOfContentsList = document.querySelector('#helfi-toc-table-of-contents-list > ul');
const mainContent = document.querySelector('main.layout-main-wrapper');
const reservedElems = document.querySelectorAll('[id]');
reservedElems.forEach(function (elem) {
Drupal.tableOfContents.reservedIds.push(elem.id);
});

// Craft table of contents.
once('table-of-contents', Drupal.tableOfContents.titleComponents().join(','), mainContent)
.forEach((content) => {

const { nodeName, anchorName} = Drupal.tableOfContents.createTableOfContentElements(content, []);

// Create table of contents if component is enabled.
if (tableOfContentsList && nodeName === 'h2') {
Expand All @@ -179,20 +233,11 @@
listItem.appendChild(link);
tableOfContentsList.appendChild(listItem);
}
// Create anchor links.
content.setAttribute('id', anchorName);
content.setAttribute('tabindex', '-1'); // Set tabindex to -1 to avoid issues with screen readers.
});
}
);

if (tableOfContents) {
// Remove loading text and noscript element.
const removeElements = tableOfContents.parentElement.querySelectorAll('.js-remove');
removeElements.forEach(function (element) {
element.remove();
});

// Update toc visible.
tableOfContents.setAttribute('data-js', 'true');
Drupal.tableOfContents.updateTOC(tableOfContents);
}
},
};
Expand Down

0 comments on commit 20d94f8

Please sign in to comment.