3 changed files with 82 additions and 0 deletions
Binary file not shown.
@ -0,0 +1,81 @@ |
|||||||
|
/** |
||||||
|
* @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); |
||||||
Loading…
Reference in new issue