mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
0139b91b5d
* chore: Replace lodash with es-toolkit Migrate all direct lodash imports to es-toolkit/compat for a smaller, faster, lodash-compatible utility library. Transitive lodash usage from other packages remains unchanged. * fix: Restore isPlainObject semantics in CanCan policy The lodash migration aliased `isObject` to `lodash/isPlainObject` and the codemod incorrectly mapped the local name to es-toolkit's `isObject`, which also returns true for arrays and functions. This caused condition objects in policy definitions to be skipped, breaking authorization checks across the codebase. * fix: Restore unicode-aware length counting in validators es-toolkit/compat's size() returns string.length, while lodash's _.size() counts unicode code points. Switch to [...value].length to preserve the previous behavior so multi-byte characters like emoji count as one.
40 lines
1.1 KiB
TypeScript
40 lines
1.1 KiB
TypeScript
import { escape } from "es-toolkit/compat";
|
||
import type { Node } from "prosemirror-model";
|
||
import slugify from "slugify";
|
||
|
||
const cache = new Map<string, string>();
|
||
|
||
// Slugify, escape, and remove periods from headings so that they are
|
||
// compatible with both url hashes AND dom ID's (querySelector does not like
|
||
// ID's that begin with a number or a period, for example).
|
||
function safeSlugify(text: string) {
|
||
if (cache.has(text)) {
|
||
return cache.get(text) as string;
|
||
}
|
||
|
||
const slug = `h-${escape(
|
||
slugify(text, {
|
||
remove: /[!"#$%&'.()*+,/:;<=>?@[\]\\^_`{|}~]/g,
|
||
lower: true,
|
||
})
|
||
)}`;
|
||
|
||
cache.set(text, slug);
|
||
return slug;
|
||
}
|
||
|
||
// calculates a unique slug for this heading based on it's text and position
|
||
// in the document that is as stable as possible
|
||
export default function headingToSlug(node: Node, index = 0) {
|
||
const slugified = safeSlugify(node.textContent);
|
||
if (index === 0) {
|
||
return slugified;
|
||
}
|
||
return `${slugified}-${index}`;
|
||
}
|
||
|
||
export function headingToPersistenceKey(node: Node, id?: string) {
|
||
const slug = headingToSlug(node);
|
||
return `rme-${id || window?.location.pathname}–${slug}`;
|
||
}
|