D10 theme for Booklives
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

81 lines
2.7 KiB

/**
* @file
* Cosmetic fix: converts straight quotes to typographic (curly) quotes
* in rendered field markup, on page load / AJAX render.
*
* IMPORTANT: This is a display-only patch. It does NOT change the stored
* field data in the database — it only rewrites text nodes in the DOM
* after render. Treat it as a stopgap until the migrated content itself
* is corrected; remove this once that's done.
*
* Adjust SELECTOR below to match whichever field wrappers actually need
* this (e.g. body fields, specific content types). Keep it as narrow as
* you reasonably can — this walks every text node inside each match.
*/
(function (Drupal, once) {
"use strict";
// CHANGE THIS to target only the fields that need it.
const SELECTOR = ".field--name-field-history-note";
// Tags whose text content should never be touched.
const SKIP_TAGS = new Set([
"SCRIPT",
"STYLE",
"TEXTAREA",
"INPUT",
"CODE",
"PRE",
]);
/**
* Converts straight quotes/apostrophes in a string to curly equivalents.
*
* Heuristic (same logic SmartyPants-style tools use):
* - A straight double quote at the start of the string, or preceded by
* whitespace / an opening bracket / a dash, is an OPENING quote.
* Everything else is a CLOSING quote.
* - A straight single quote in the same "opening" positions becomes an
* opening single quote (‘); every other straight single quote —
* including apostrophes in contractions like don't — becomes the
* closing/apostrophe glyph (’), which is correct either way.
*/
function smartenQuotes(text) {
text = text.replace(/(^|[\s([{\-\u2014])"/g, "$1\u201C"); // opening “
text = text.replace(/"/g, "\u201D"); // remaining → ”
text = text.replace(/(^|[\s([{\-\u2014])'/g, "$1\u2018"); // opening ‘
text = text.replace(/'/g, "\u2019"); // remaining → ’ (also apostrophes)
return text;
}
function fixTextNodesIn(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode: function (node) {
if (SKIP_TAGS.has(node.parentNode.nodeName)) {
return NodeFilter.FILTER_REJECT;
}
if (!/["']/.test(node.nodeValue)) {
return NodeFilter.FILTER_SKIP;
}
return NodeFilter.FILTER_ACCEPT;
},
});
const nodes = [];
let n;
while ((n = walker.nextNode())) {
nodes.push(n);
}
nodes.forEach(function (node) {
node.nodeValue = smartenQuotes(node.nodeValue);
});
}
Drupal.behaviors.smartQuotesCosmetic = {
attach: function (context) {
once("smart-quotes-cosmetic", SELECTOR, context).forEach(function (el) {
fixTextNodesIn(el);
});
},
};
})(Drupal, once);