Files
chrome-extensions-samples/functional-samples/tutorial.reading-time/scripts/content.js
Oliver Dunk b55612ae64 Handle SPA navigations in reading time example. (#1496)
In the move to new infrastructure for https://developer.chrome.com/, the
site became an SPA which can soft navigate outside of browser
navigations. Therefore, we need to update the sample to watch for
changes to content after the content script is injected.
2025-06-23 09:34:53 +02:00

72 lines
2.8 KiB
JavaScript

// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
function renderReadingTime(article) {
// If we weren't provided an article, we don't need to render anything.
if (!article) {
return;
}
const text = article.textContent;
/**
* Regular expression to find all "words" in a string.
*
* Here, a "word" is a sequence of one or more non-whitespace characters in a row. We don't use the
* regular expression character class "\w" to match against "word characters" because it only
* matches against the Latin alphabet. Instead, we match against any sequence of characters that
* *are not* a whitespace characters. See the below link for more information.
*
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
*/
const wordMatchRegExp = /[^\s]+/g;
const words = text.matchAll(wordMatchRegExp);
// matchAll returns an iterator, convert to array to get word count
const wordCount = [...words].length;
const readingTime = Math.round(wordCount / 200);
const badge = document.createElement('p');
// Use the same styling as the publish information in an article's header
badge.classList.add('color-secondary-text', 'type--caption');
badge.textContent = `⏱️ ${readingTime} min read`;
// Support for API reference docs
const heading = article.querySelector('h1');
// Support for article docs with date
const date = article.querySelector('time')?.parentNode;
// https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
(date ?? heading).insertAdjacentElement('afterend', badge);
}
renderReadingTime(document.querySelector('article'));
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// If a new article was added.
for (const node of mutation.addedNodes) {
if (node instanceof Element && node.tagName === 'ARTICLE') {
// Render the reading time for this particular article.
renderReadingTime(node);
}
}
}
});
// https://developer.chrome.com/ is a SPA (Single Page Application) so can
// update the address bar and render new content without reloading. Our content
// script won't be reinjected when this happens, so we need to watch for
// changes to the content.
observer.observe(document.querySelector('devsite-content'), {
childList: true
});