Files

67 lines
2.3 KiB
JavaScript
Raw Permalink Normal View History

'use strict';
/**
* Keyword matching — which of Overflow's keywords a slide's notes carry.
*
* Pure functions, no I/O, so keywords.selftest.js can pin every rule down:
*
* cleanNotes(raw) → the notes with ProPresenter's invisible padding removed
* keywordMatches(notes, keyword) → boolean
* matchKeywords(notes, keywords) → every keyword (in the order given) the notes carry
* sameSet(a, b) → the two keyword lists hold the same keywords
*
* The rule is Overflow's own helper's: the notes are split into whitespace-
* separated words, each stripped of surrounding punctuation and lower-cased,
* and a keyword fires when it is one of those words. So `give` fires on
* "give", "Give!", "#give" and "(give)", and not on "forgiveness", "giveaway"
* or "give_now". Phrases work too, bounded the same way on both ends —
* something the helper cannot do, but nothing it would have matched is lost.
*/
const ZERO_WIDTH = /[​-‍⁠]/g;
function cleanNotes(raw) {
return String(raw ?? '').replace(ZERO_WIDTH, '').replace(/\r\n?/g, '\n');
}
function norm(s) {
return String(s ?? '').trim().toLowerCase().replace(/\s+/g, ' ');
}
function escapeRe(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function keywordMatches(notes, keyword) {
const n = cleanNotes(notes);
const k = norm(keyword);
if (!k || !n.trim()) return false;
const flat = n.toLowerCase().replace(/\s+/g, ' ');
const re = new RegExp(`(?:^|[^\\p{L}\\p{N}_])${escapeRe(k)}(?=$|[^\\p{L}\\p{N}_])`, 'u');
return re.test(flat);
}
/** Every keyword the notes carry, in the order the list gives them, each once. Entries may be strings or objects with a `keyword`. */
function matchKeywords(notes, keywords) {
const out = [];
const seen = new Set();
for (const raw of keywords || []) {
const k = typeof raw === 'string' ? raw : raw && raw.keyword;
const key = norm(k);
if (!key || seen.has(key)) continue;
if (keywordMatches(notes, k)) {
seen.add(key);
out.push(String(k).trim());
}
}
return out;
}
function sameSet(a, b) {
const x = [...(a || [])].map(norm).sort();
const y = [...(b || [])].map(norm).sort();
return x.length === y.length && x.every((v, i) => v === y[i]);
}
module.exports = { cleanNotes, norm, keywordMatches, matchKeywords, sameSet };