v0.2.91
This commit is contained in:
1 parent
4da41a1404
commit
5cb6960b04
31 files changed
+1611
-2123
No files matched your search
+68
-7
@@ -60,7 +60,61 @@ export function parseAuthorship(content, raw) {
|
||||
}
|
||||
|
||||
export function serializeAuthorship(model, contentLength) {
|
||||
return JSON.stringify({ version: VERSION, spans: normalize(model?.spans, contentLength) });
|
||||
const canonical = canonicalSpans(model, contentLength);
|
||||
const spans = canonical
|
||||
? canonical.map(span => ({ start: span.start, end: span.end, owner: span.owner }))
|
||||
: normalize(model?.spans, contentLength);
|
||||
return JSON.stringify({ version: VERSION, spans });
|
||||
}
|
||||
|
||||
function canonicalSpans(model, length) {
|
||||
const spans = model?.spans;
|
||||
if (!Array.isArray(spans)) return null;
|
||||
let previousEnd = 0, previousOwner = "";
|
||||
for (const span of spans) {
|
||||
if (!span || typeof span.owner !== "string" || !span.owner
|
||||
|| !Number.isFinite(span.start) || !Number.isFinite(span.end)
|
||||
|| span.start < previousEnd || span.end <= span.start || span.end > length
|
||||
|| (span.start === previousEnd && span.owner === previousOwner)) return null;
|
||||
previousEnd = span.end;
|
||||
previousOwner = span.owner;
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
|
||||
function appendCanonicalSpan(result, start, end, owner) {
|
||||
if (!owner || end <= start) return;
|
||||
const previous = result.at(-1);
|
||||
if (previous?.owner === owner && start <= previous.end) {
|
||||
previous.end = Math.max(previous.end, end);
|
||||
} else result.push({ start, end, owner });
|
||||
}
|
||||
|
||||
function fastAuthorshipEdit(spans, prefix, oldSuffix, insertedLength, delta, owner) {
|
||||
const result = [];
|
||||
let inserted = false;
|
||||
const insert = () => {
|
||||
if (!inserted) {
|
||||
appendCanonicalSpan(result, prefix, prefix + insertedLength, owner ? String(owner) : "");
|
||||
inserted = true;
|
||||
}
|
||||
};
|
||||
for (const span of spans) {
|
||||
if (span.end <= prefix) {
|
||||
appendCanonicalSpan(result, span.start, span.end, span.owner);
|
||||
} else if (span.start >= oldSuffix) {
|
||||
insert();
|
||||
appendCanonicalSpan(result, span.start + delta, span.end + delta, span.owner);
|
||||
} else {
|
||||
if (span.start < prefix) appendCanonicalSpan(result, span.start, prefix, span.owner);
|
||||
insert();
|
||||
if (span.end > oldSuffix) {
|
||||
appendCanonicalSpan(result, prefix + insertedLength, span.end + delta, span.owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
insert();
|
||||
return { version: VERSION, spans: result };
|
||||
}
|
||||
|
||||
export function applyAuthorshipEdit(model, previousText, nextText, owner) {
|
||||
@@ -79,6 +133,8 @@ export function applyAuthorshipEdit(model, previousText, nextText, owner) {
|
||||
const removedLength = oldSuffix - prefix;
|
||||
const insertedLength = newSuffix - prefix;
|
||||
const delta = insertedLength - removedLength;
|
||||
const canonical = canonicalSpans(model, previousText.length);
|
||||
if (canonical) return fastAuthorshipEdit(canonical, prefix, oldSuffix, insertedLength, delta, owner);
|
||||
const updated = [];
|
||||
|
||||
for (const source of normalize(model?.spans, previousText.length)) {
|
||||
@@ -99,13 +155,22 @@ export function applyAuthorshipEdit(model, previousText, nextText, owner) {
|
||||
}
|
||||
|
||||
export function replaceAuthorshipOwner(model, matcher, replacement, contentLength) {
|
||||
const canonical = canonicalSpans(model, contentLength);
|
||||
if (canonical) {
|
||||
const spans = [];
|
||||
for (const span of canonical) {
|
||||
const owner = matcher(span.owner) ? String(replacement || "") : span.owner;
|
||||
appendCanonicalSpan(spans, span.start, span.end, owner);
|
||||
}
|
||||
return { version: VERSION, spans };
|
||||
}
|
||||
|
||||
return {
|
||||
version: VERSION,
|
||||
spans: normalize((model?.spans || []).map(span => matcher(span.owner) ? { ...span, owner: replacement } : span), contentLength),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function authorshipOwners(model) {
|
||||
const owners = new Map();
|
||||
for (const span of model?.spans || []) {
|
||||
@@ -120,13 +185,11 @@ export function lineAuthors(content, model) {
|
||||
const starts = [0];
|
||||
for (let i = 0; i < content.length; i++) if (content.charCodeAt(i) === 10) starts.push(i + 1);
|
||||
|
||||
// Parsed authorship spans are sorted and non-overlapping. Retain the old
|
||||
// result for non-canonical callers instead of silently changing semantics.
|
||||
let canonical = true;
|
||||
for (let i = 0, previousEnd = 0; i < spans.length; i++) {
|
||||
const span = spans[i];
|
||||
if (!Number.isFinite(span?.start) || !Number.isFinite(span?.end)
|
||||
|| span.start < previousEnd || span.end < span.start) {
|
||||
|| span.start < previousEnd || span.end < span.start) {
|
||||
canonical = false;
|
||||
break;
|
||||
}
|
||||
@@ -187,8 +250,6 @@ export function syncAuthorshipLayer(layer, editor) {
|
||||
const canvas = layer.querySelector(".authorship-canvas");
|
||||
if (!canvas) return;
|
||||
canvas.style.transform = `translate3d(${-editor.scrollLeft}px, ${-editor.scrollTop}px, 0)`;
|
||||
// The canvas already mirrors the textarea scroll through its transform.
|
||||
// Keeping the overlay element itself at zero prevents a double offset.
|
||||
layer.scrollTop = 0;
|
||||
layer.scrollLeft = 0;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user