alinea
Version:
[](https://npmjs.org/package/alinea) [](https://packagephobia.com/result?p=alinea)
1,502 lines (1,491 loc) • 47.5 kB
JavaScript
import {
EditorContent,
FloatingMenu,
NodeViewWrapper,
ReactNodeViewRenderer
} from "../../chunks/chunk-OBYSELPT.js";
import "../../chunks/chunk-I5C4WAC4.js";
import {
Extension,
Fragment,
Node,
Plugin,
PluginKey,
Slice,
TextSelection,
mergeAttributes
} from "../../chunks/chunk-MDIOFKJQ.js";
import {
ContentFormat,
ContentString,
ContentType,
Item,
RelativePosition,
Snapshot,
UndoManager,
YText,
YXmlElement,
YXmlText,
create,
createAbsolutePositionFromRelativePosition,
createDeleteSet,
createID,
createRelativePositionFromTypeIndex,
createSnapshot,
doc,
findRootTypeKey,
isBrowser,
isDeleted,
isParentOf,
iterateDeletedStructs,
keys,
oneOf,
snapshot,
timeout,
typeListToArraySnapshot
} from "../../chunks/chunk-OYP4EJOA.js";
import {
methodUnimplemented,
min,
unexpectedCase
} from "../../chunks/chunk-O6EXLFU2.js";
import "../../chunks/chunk-U5RRZUYZ.js";
// node_modules/lib0/mutex.js
var createMutex = () => {
let token = true;
return (f, g) => {
if (token) {
token = false;
try {
f();
} finally {
token = true;
}
} else if (g !== void 0) {
g();
}
};
};
// node_modules/lib0/diff.js
var highSurrogateRegex = /[\uD800-\uDBFF]/;
var lowSurrogateRegex = /[\uDC00-\uDFFF]/;
var simpleDiffString = (a, b) => {
let left = 0;
let right = 0;
while (left < a.length && left < b.length && a[left] === b[left]) {
left++;
}
if (left > 0 && highSurrogateRegex.test(a[left - 1]))
left--;
while (right + left < a.length && right + left < b.length && a[a.length - right - 1] === b[b.length - right - 1]) {
right++;
}
if (right > 0 && lowSurrogateRegex.test(a[a.length - right]))
right--;
return {
index: left,
remove: a.length - left - right,
insert: b.slice(left, b.length - right)
};
};
var simpleDiff = simpleDiffString;
// node_modules/y-prosemirror/src/plugins/keys.js
var ySyncPluginKey = new PluginKey("y-sync");
var yUndoPluginKey = new PluginKey("y-undo");
var yCursorPluginKey = new PluginKey("yjs-cursor");
// node_modules/y-prosemirror/src/plugins/sync-plugin.js
var isVisible = (item, snapshot2) => snapshot2 === void 0 ? !item.deleted : snapshot2.sv.has(item.id.client) && /** @type {number} */
snapshot2.sv.get(item.id.client) > item.id.clock && !isDeleted(snapshot2.ds, item.id);
var defaultColors = [{ light: "#ecd44433", dark: "#ecd444" }];
var getUserColor = (colorMapping, colors, user) => {
if (!colorMapping.has(user)) {
if (colorMapping.size < colors.length) {
const usedColors = create();
colorMapping.forEach((color) => usedColors.add(color));
colors = colors.filter((color) => !usedColors.has(color));
}
colorMapping.set(user, oneOf(colors));
}
return (
/** @type {ColorDef} */
colorMapping.get(user)
);
};
var ySyncPlugin = (yXmlFragment, {
colors = defaultColors,
colorMapping = /* @__PURE__ */ new Map(),
permanentUserData = null,
onFirstRender = () => {
}
} = {}) => {
let changedInitialContent = false;
let rerenderTimeout;
const plugin = new Plugin({
props: {
editable: (state) => {
const syncState = ySyncPluginKey.getState(state);
return syncState.snapshot == null && syncState.prevSnapshot == null;
}
},
key: ySyncPluginKey,
state: {
/**
* @returns {any}
*/
init: (_initargs, _state) => {
return {
type: yXmlFragment,
doc: yXmlFragment.doc,
binding: null,
snapshot: null,
prevSnapshot: null,
isChangeOrigin: false,
isUndoRedoOperation: false,
addToHistory: true,
colors,
colorMapping,
permanentUserData
};
},
apply: (tr, pluginState) => {
const change = tr.getMeta(ySyncPluginKey);
if (change !== void 0) {
pluginState = Object.assign({}, pluginState);
for (const key in change) {
pluginState[key] = change[key];
}
}
pluginState.addToHistory = tr.getMeta("addToHistory") !== false;
pluginState.isChangeOrigin = change !== void 0 && !!change.isChangeOrigin;
pluginState.isUndoRedoOperation = change !== void 0 && !!change.isChangeOrigin && !!change.isUndoRedoOperation;
if (pluginState.binding !== null) {
if (change !== void 0 && (change.snapshot != null || change.prevSnapshot != null)) {
timeout(0, () => {
if (pluginState.binding == null || pluginState.binding.isDestroyed) {
return;
}
if (change.restore == null) {
pluginState.binding._renderSnapshot(
change.snapshot,
change.prevSnapshot,
pluginState
);
} else {
pluginState.binding._renderSnapshot(
change.snapshot,
change.snapshot,
pluginState
);
delete pluginState.restore;
delete pluginState.snapshot;
delete pluginState.prevSnapshot;
pluginState.binding.mux(() => {
pluginState.binding._prosemirrorChanged(
pluginState.binding.prosemirrorView.state.doc
);
});
}
});
}
}
return pluginState;
}
},
view: (view) => {
const binding = new ProsemirrorBinding(yXmlFragment, view);
if (rerenderTimeout != null) {
rerenderTimeout.destroy();
}
rerenderTimeout = timeout(0, () => {
binding._forceRerender();
view.dispatch(view.state.tr.setMeta(ySyncPluginKey, { binding }));
onFirstRender();
});
return {
update: () => {
const pluginState = plugin.getState(view.state);
if (pluginState.snapshot == null && pluginState.prevSnapshot == null) {
if (changedInitialContent || view.state.doc.content.findDiffStart(
view.state.doc.type.createAndFill().content
) !== null) {
changedInitialContent = true;
if (pluginState.addToHistory === false && !pluginState.isChangeOrigin) {
const yUndoPluginState = yUndoPluginKey.getState(view.state);
const um = yUndoPluginState && yUndoPluginState.undoManager;
if (um) {
um.stopCapturing();
}
}
binding.mux(() => {
pluginState.doc.transact((tr) => {
tr.meta.set("addToHistory", pluginState.addToHistory);
binding._prosemirrorChanged(view.state.doc);
}, ySyncPluginKey);
});
}
}
},
destroy: () => {
rerenderTimeout.destroy();
binding.destroy();
}
};
}
});
return plugin;
};
var restoreRelativeSelection = (tr, relSel, binding) => {
if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
const anchor = relativePositionToAbsolutePosition(
binding.doc,
binding.type,
relSel.anchor,
binding.mapping
);
const head = relativePositionToAbsolutePosition(
binding.doc,
binding.type,
relSel.head,
binding.mapping
);
if (anchor !== null && head !== null) {
tr = tr.setSelection(TextSelection.create(tr.doc, anchor, head));
}
}
};
var getRelativeSelection = (pmbinding, state) => ({
anchor: absolutePositionToRelativePosition(
state.selection.anchor,
pmbinding.type,
pmbinding.mapping
),
head: absolutePositionToRelativePosition(
state.selection.head,
pmbinding.type,
pmbinding.mapping
)
});
var ProsemirrorBinding = class {
/**
* @param {Y.XmlFragment} yXmlFragment The bind source
* @param {any} prosemirrorView The target binding
*/
constructor(yXmlFragment, prosemirrorView) {
this.type = yXmlFragment;
this.prosemirrorView = prosemirrorView;
this.mux = createMutex();
this.isDestroyed = false;
this.mapping = /* @__PURE__ */ new Map();
this._observeFunction = this._typeChanged.bind(this);
this.doc = yXmlFragment.doc;
this.beforeTransactionSelection = null;
this.beforeAllTransactions = () => {
if (this.beforeTransactionSelection === null) {
this.beforeTransactionSelection = getRelativeSelection(
this,
prosemirrorView.state
);
}
};
this.afterAllTransactions = () => {
this.beforeTransactionSelection = null;
};
this.doc.on("beforeAllTransactions", this.beforeAllTransactions);
this.doc.on("afterAllTransactions", this.afterAllTransactions);
yXmlFragment.observeDeep(this._observeFunction);
this._domSelectionInView = null;
}
/**
* Create a transaction for changing the prosemirror state.
*
* @returns
*/
get _tr() {
return this.prosemirrorView.state.tr.setMeta("addToHistory", false);
}
_isLocalCursorInView() {
if (!this.prosemirrorView.hasFocus())
return false;
if (isBrowser && this._domSelectionInView === null) {
timeout(0, () => {
this._domSelectionInView = null;
});
this._domSelectionInView = this._isDomSelectionInView();
}
return this._domSelectionInView;
}
_isDomSelectionInView() {
const selection = this.prosemirrorView._root.getSelection();
const range = this.prosemirrorView._root.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
const rects = range.getClientRects();
if (rects.length === 0) {
if (range.startContainer && range.collapsed) {
range.selectNodeContents(range.startContainer);
}
}
const bounding = range.getBoundingClientRect();
const documentElement = doc.documentElement;
return bounding.bottom >= 0 && bounding.right >= 0 && bounding.left <= (window.innerWidth || documentElement.clientWidth || 0) && bounding.top <= (window.innerHeight || documentElement.clientHeight || 0);
}
/**
* @param {Y.Snapshot} snapshot
* @param {Y.Snapshot} prevSnapshot
*/
renderSnapshot(snapshot2, prevSnapshot) {
if (!prevSnapshot) {
prevSnapshot = createSnapshot(createDeleteSet(), /* @__PURE__ */ new Map());
}
this.prosemirrorView.dispatch(
this._tr.setMeta(ySyncPluginKey, { snapshot: snapshot2, prevSnapshot })
);
}
unrenderSnapshot() {
this.mapping = /* @__PURE__ */ new Map();
this.mux(() => {
const fragmentContent = this.type.toArray().map(
(t) => createNodeFromYElement(
/** @type {Y.XmlElement} */
t,
this.prosemirrorView.state.schema,
this.mapping
)
).filter((n) => n !== null);
const tr = this._tr.replace(
0,
this.prosemirrorView.state.doc.content.size,
new Slice(Fragment.from(fragmentContent), 0, 0)
);
tr.setMeta(ySyncPluginKey, { snapshot: null, prevSnapshot: null });
this.prosemirrorView.dispatch(tr);
});
}
_forceRerender() {
this.mapping = /* @__PURE__ */ new Map();
this.mux(() => {
const fragmentContent = this.type.toArray().map(
(t) => createNodeFromYElement(
/** @type {Y.XmlElement} */
t,
this.prosemirrorView.state.schema,
this.mapping
)
).filter((n) => n !== null);
const tr = this._tr.replace(
0,
this.prosemirrorView.state.doc.content.size,
new Slice(Fragment.from(fragmentContent), 0, 0)
);
this.prosemirrorView.dispatch(
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true })
);
});
}
/**
* @param {Y.Snapshot} snapshot
* @param {Y.Snapshot} prevSnapshot
* @param {Object} pluginState
*/
_renderSnapshot(snapshot2, prevSnapshot, pluginState) {
if (!snapshot2) {
snapshot2 = snapshot(this.doc);
}
this.mapping = /* @__PURE__ */ new Map();
this.mux(() => {
this.doc.transact((transaction) => {
const pud = pluginState.permanentUserData;
if (pud) {
pud.dss.forEach((ds) => {
iterateDeletedStructs(transaction, ds, (_item) => {
});
});
}
const computeYChange = (type, id) => {
const user = type === "added" ? pud.getUserByClientId(id.client) : pud.getUserByDeletedId(id);
return {
user,
type,
color: getUserColor(
pluginState.colorMapping,
pluginState.colors,
user
)
};
};
const fragmentContent = typeListToArraySnapshot(
this.type,
new Snapshot(prevSnapshot.ds, snapshot2.sv)
).map((t) => {
if (!t._item.deleted || isVisible(t._item, snapshot2) || isVisible(t._item, prevSnapshot)) {
return createNodeFromYElement(
t,
this.prosemirrorView.state.schema,
/* @__PURE__ */ new Map(),
snapshot2,
prevSnapshot,
computeYChange
);
} else {
return null;
}
}).filter((n) => n !== null);
const tr = this._tr.replace(
0,
this.prosemirrorView.state.doc.content.size,
new Slice(Fragment.from(fragmentContent), 0, 0)
);
this.prosemirrorView.dispatch(
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true })
);
}, ySyncPluginKey);
});
}
/**
* @param {Array<Y.YEvent<any>>} events
* @param {Y.Transaction} transaction
*/
_typeChanged(events, transaction) {
const syncState = ySyncPluginKey.getState(this.prosemirrorView.state);
if (events.length === 0 || syncState.snapshot != null || syncState.prevSnapshot != null) {
this.renderSnapshot(syncState.snapshot, syncState.prevSnapshot);
return;
}
this.mux(() => {
const delType = (_, type) => this.mapping.delete(type);
iterateDeletedStructs(
transaction,
transaction.deleteSet,
(struct) => {
if (struct.constructor === Item) {
const type = (
/** @type {Y.ContentType} */
/** @type {Y.Item} */
struct.content.type
);
type && this.mapping.delete(type);
}
}
);
transaction.changed.forEach(delType);
transaction.changedParentTypes.forEach(delType);
const fragmentContent = this.type.toArray().map(
(t) => createNodeIfNotExists(
/** @type {Y.XmlElement | Y.XmlHook} */
t,
this.prosemirrorView.state.schema,
this.mapping
)
).filter((n) => n !== null);
let tr = this._tr.replace(
0,
this.prosemirrorView.state.doc.content.size,
new Slice(Fragment.from(fragmentContent), 0, 0)
);
restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, isUndoRedoOperation: transaction.origin instanceof UndoManager });
if (this.beforeTransactionSelection !== null && this._isLocalCursorInView()) {
tr.scrollIntoView();
}
this.prosemirrorView.dispatch(tr);
});
}
_prosemirrorChanged(doc2) {
this.doc.transact(() => {
updateYFragment(this.doc, this.type, doc2, this.mapping);
this.beforeTransactionSelection = getRelativeSelection(
this,
this.prosemirrorView.state
);
}, ySyncPluginKey);
}
destroy() {
this.isDestroyed = true;
this.type.unobserveDeep(this._observeFunction);
this.doc.off("beforeAllTransactions", this.beforeAllTransactions);
this.doc.off("afterAllTransactions", this.afterAllTransactions);
}
};
var createNodeIfNotExists = (el, schema, mapping, snapshot2, prevSnapshot, computeYChange) => {
const node = (
/** @type {PModel.Node} */
mapping.get(el)
);
if (node === void 0) {
if (el instanceof YXmlElement) {
return createNodeFromYElement(
el,
schema,
mapping,
snapshot2,
prevSnapshot,
computeYChange
);
} else {
throw methodUnimplemented();
}
}
return node;
};
var createNodeFromYElement = (el, schema, mapping, snapshot2, prevSnapshot, computeYChange) => {
const children = [];
const createChildren = (type) => {
if (type.constructor === YXmlElement) {
const n = createNodeIfNotExists(
type,
schema,
mapping,
snapshot2,
prevSnapshot,
computeYChange
);
if (n !== null) {
children.push(n);
}
} else {
const ns = createTextNodesFromYText(
type,
schema,
mapping,
snapshot2,
prevSnapshot,
computeYChange
);
if (ns !== null) {
ns.forEach((textchild) => {
if (textchild !== null) {
children.push(textchild);
}
});
}
}
};
if (snapshot2 === void 0 || prevSnapshot === void 0) {
el.toArray().forEach(createChildren);
} else {
typeListToArraySnapshot(el, new Snapshot(prevSnapshot.ds, snapshot2.sv)).forEach(createChildren);
}
try {
const attrs = el.getAttributes(snapshot2);
if (snapshot2 !== void 0) {
if (!isVisible(
/** @type {Y.Item} */
el._item,
snapshot2
)) {
attrs.ychange = computeYChange ? computeYChange(
"removed",
/** @type {Y.Item} */
el._item.id
) : { type: "removed" };
} else if (!isVisible(
/** @type {Y.Item} */
el._item,
prevSnapshot
)) {
attrs.ychange = computeYChange ? computeYChange(
"added",
/** @type {Y.Item} */
el._item.id
) : { type: "added" };
}
}
const node = schema.node(el.nodeName, attrs, children);
mapping.set(el, node);
return node;
} catch (e) {
el.doc.transact((transaction) => {
el._item.delete(transaction);
}, ySyncPluginKey);
mapping.delete(el);
return null;
}
};
var createTextNodesFromYText = (text, schema, _mapping, snapshot2, prevSnapshot, computeYChange) => {
const nodes = [];
const deltas = text.toDelta(snapshot2, prevSnapshot, computeYChange);
try {
for (let i = 0; i < deltas.length; i++) {
const delta = deltas[i];
const marks = [];
for (const markName in delta.attributes) {
marks.push(schema.mark(markName, delta.attributes[markName]));
}
nodes.push(schema.text(delta.insert, marks));
}
} catch (e) {
text.doc.transact((transaction) => {
text._item.delete(transaction);
}, ySyncPluginKey);
return null;
}
return nodes;
};
var createTypeFromTextNodes = (nodes, mapping) => {
const type = new YXmlText();
const delta = nodes.map((node) => ({
// @ts-ignore
insert: node.text,
attributes: marksToAttributes(node.marks)
}));
type.applyDelta(delta);
mapping.set(type, nodes);
return type;
};
var createTypeFromElementNode = (node, mapping) => {
const type = new YXmlElement(node.type.name);
for (const key in node.attrs) {
const val = node.attrs[key];
if (val !== null && key !== "ychange") {
type.setAttribute(key, val);
}
}
type.insert(
0,
normalizePNodeContent(node).map(
(n) => createTypeFromTextOrElementNode(n, mapping)
)
);
mapping.set(type, node);
return type;
};
var createTypeFromTextOrElementNode = (node, mapping) => node instanceof Array ? createTypeFromTextNodes(node, mapping) : createTypeFromElementNode(node, mapping);
var isObject = (val) => typeof val === "object" && val !== null;
var equalAttrs = (pattrs, yattrs) => {
const keys2 = Object.keys(pattrs).filter((key) => pattrs[key] !== null);
let eq = keys2.length === Object.keys(yattrs).filter((key) => yattrs[key] !== null).length;
for (let i = 0; i < keys2.length && eq; i++) {
const key = keys2[i];
const l = pattrs[key];
const r = yattrs[key];
eq = key === "ychange" || l === r || isObject(l) && isObject(r) && equalAttrs(l, r);
}
return eq;
};
var normalizePNodeContent = (pnode) => {
const c = pnode.content.content;
const res = [];
for (let i = 0; i < c.length; i++) {
const n = c[i];
if (n.isText) {
const textNodes = [];
for (let tnode = c[i]; i < c.length && tnode.isText; tnode = c[++i]) {
textNodes.push(tnode);
}
i--;
res.push(textNodes);
} else {
res.push(n);
}
}
return res;
};
var equalYTextPText = (ytext, ptexts) => {
const delta = ytext.toDelta();
return delta.length === ptexts.length && delta.every(
(d, i) => d.insert === /** @type {any} */
ptexts[i].text && keys(d.attributes || {}).length === ptexts[i].marks.length && ptexts[i].marks.every(
(mark) => equalAttrs(d.attributes[mark.type.name] || {}, mark.attrs)
)
);
};
var equalYTypePNode = (ytype, pnode) => {
if (ytype instanceof YXmlElement && !(pnode instanceof Array) && matchNodeName(ytype, pnode)) {
const normalizedContent = normalizePNodeContent(pnode);
return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), pnode.attrs) && ytype.toArray().every(
(ychild, i) => equalYTypePNode(ychild, normalizedContent[i])
);
}
return ytype instanceof YXmlText && pnode instanceof Array && equalYTextPText(ytype, pnode);
};
var mappedIdentity = (mapped, pcontent) => mapped === pcontent || mapped instanceof Array && pcontent instanceof Array && mapped.length === pcontent.length && mapped.every(
(a, i) => pcontent[i] === a
);
var computeChildEqualityFactor = (ytype, pnode, mapping) => {
const yChildren = ytype.toArray();
const pChildren = normalizePNodeContent(pnode);
const pChildCnt = pChildren.length;
const yChildCnt = yChildren.length;
const minCnt = min(yChildCnt, pChildCnt);
let left = 0;
let right = 0;
let foundMappedChild = false;
for (; left < minCnt; left++) {
const leftY = yChildren[left];
const leftP = pChildren[left];
if (mappedIdentity(mapping.get(leftY), leftP)) {
foundMappedChild = true;
} else if (!equalYTypePNode(leftY, leftP)) {
break;
}
}
for (; left + right < minCnt; right++) {
const rightY = yChildren[yChildCnt - right - 1];
const rightP = pChildren[pChildCnt - right - 1];
if (mappedIdentity(mapping.get(rightY), rightP)) {
foundMappedChild = true;
} else if (!equalYTypePNode(rightY, rightP)) {
break;
}
}
return {
equalityFactor: left + right,
foundMappedChild
};
};
var ytextTrans = (ytext) => {
let str = "";
let n = ytext._start;
const nAttrs = {};
while (n !== null) {
if (!n.deleted) {
if (n.countable && n.content instanceof ContentString) {
str += n.content.str;
} else if (n.content instanceof ContentFormat) {
nAttrs[n.content.key] = null;
}
}
n = n.right;
}
return {
str,
nAttrs
};
};
var updateYText = (ytext, ptexts, mapping) => {
mapping.set(ytext, ptexts);
const { nAttrs, str } = ytextTrans(ytext);
const content = ptexts.map((p) => ({
insert: (
/** @type {any} */
p.text
),
attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks))
}));
const { insert, remove, index } = simpleDiff(
str,
content.map((c) => c.insert).join("")
);
ytext.delete(index, remove);
ytext.insert(index, insert);
ytext.applyDelta(
content.map((c) => ({ retain: c.insert.length, attributes: c.attributes }))
);
};
var marksToAttributes = (marks) => {
const pattrs = {};
marks.forEach((mark) => {
if (mark.type.name !== "ychange") {
pattrs[mark.type.name] = mark.attrs;
}
});
return pattrs;
};
var updateYFragment = (y, yDomFragment, pNode, mapping) => {
if (yDomFragment instanceof YXmlElement && yDomFragment.nodeName !== pNode.type.name) {
throw new Error("node name mismatch!");
}
mapping.set(yDomFragment, pNode);
if (yDomFragment instanceof YXmlElement) {
const yDomAttrs = yDomFragment.getAttributes();
const pAttrs = pNode.attrs;
for (const key in pAttrs) {
if (pAttrs[key] !== null) {
if (yDomAttrs[key] !== pAttrs[key] && key !== "ychange") {
yDomFragment.setAttribute(key, pAttrs[key]);
}
} else {
yDomFragment.removeAttribute(key);
}
}
for (const key in yDomAttrs) {
if (pAttrs[key] === void 0) {
yDomFragment.removeAttribute(key);
}
}
}
const pChildren = normalizePNodeContent(pNode);
const pChildCnt = pChildren.length;
const yChildren = yDomFragment.toArray();
const yChildCnt = yChildren.length;
const minCnt = min(pChildCnt, yChildCnt);
let left = 0;
let right = 0;
for (; left < minCnt; left++) {
const leftY = yChildren[left];
const leftP = pChildren[left];
if (!mappedIdentity(mapping.get(leftY), leftP)) {
if (equalYTypePNode(leftY, leftP)) {
mapping.set(leftY, leftP);
} else {
break;
}
}
}
for (; right + left + 1 < minCnt; right++) {
const rightY = yChildren[yChildCnt - right - 1];
const rightP = pChildren[pChildCnt - right - 1];
if (!mappedIdentity(mapping.get(rightY), rightP)) {
if (equalYTypePNode(rightY, rightP)) {
mapping.set(rightY, rightP);
} else {
break;
}
}
}
y.transact(() => {
while (yChildCnt - left - right > 0 && pChildCnt - left - right > 0) {
const leftY = yChildren[left];
const leftP = pChildren[left];
const rightY = yChildren[yChildCnt - right - 1];
const rightP = pChildren[pChildCnt - right - 1];
if (leftY instanceof YXmlText && leftP instanceof Array) {
if (!equalYTextPText(leftY, leftP)) {
updateYText(leftY, leftP, mapping);
}
left += 1;
} else {
let updateLeft = leftY instanceof YXmlElement && matchNodeName(leftY, leftP);
let updateRight = rightY instanceof YXmlElement && matchNodeName(rightY, rightP);
if (updateLeft && updateRight) {
const equalityLeft = computeChildEqualityFactor(
/** @type {Y.XmlElement} */
leftY,
/** @type {PModel.Node} */
leftP,
mapping
);
const equalityRight = computeChildEqualityFactor(
/** @type {Y.XmlElement} */
rightY,
/** @type {PModel.Node} */
rightP,
mapping
);
if (equalityLeft.foundMappedChild && !equalityRight.foundMappedChild) {
updateRight = false;
} else if (!equalityLeft.foundMappedChild && equalityRight.foundMappedChild) {
updateLeft = false;
} else if (equalityLeft.equalityFactor < equalityRight.equalityFactor) {
updateLeft = false;
} else {
updateRight = false;
}
}
if (updateLeft) {
updateYFragment(
y,
/** @type {Y.XmlFragment} */
leftY,
/** @type {PModel.Node} */
leftP,
mapping
);
left += 1;
} else if (updateRight) {
updateYFragment(
y,
/** @type {Y.XmlFragment} */
rightY,
/** @type {PModel.Node} */
rightP,
mapping
);
right += 1;
} else {
mapping.delete(yDomFragment.get(left));
yDomFragment.delete(left, 1);
yDomFragment.insert(left, [
createTypeFromTextOrElementNode(leftP, mapping)
]);
left += 1;
}
}
}
const yDelLen = yChildCnt - left - right;
if (yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof YXmlText) {
mapping.delete(yChildren[0]);
yChildren[0].delete(0, yChildren[0].length);
} else if (yDelLen > 0) {
yDomFragment.slice(left, left + yDelLen).forEach((type) => mapping.delete(type));
yDomFragment.delete(left, yDelLen);
}
if (left + right < pChildCnt) {
const ins = [];
for (let i = left; i < pChildCnt - right; i++) {
ins.push(createTypeFromTextOrElementNode(pChildren[i], mapping));
}
yDomFragment.insert(left, ins);
}
}, ySyncPluginKey);
};
var matchNodeName = (yElement, pNode) => !(pNode instanceof Array) && yElement.nodeName === pNode.type.name;
// node_modules/y-prosemirror/src/lib.js
var absolutePositionToRelativePosition = (pos, type, mapping) => {
if (pos === 0) {
return createRelativePositionFromTypeIndex(type, 0);
}
let n = type._first === null ? null : (
/** @type {Y.ContentType} */
type._first.content.type
);
while (n !== null && type !== n) {
if (n instanceof YXmlText) {
if (n._length >= pos) {
return createRelativePositionFromTypeIndex(n, pos);
} else {
pos -= n._length;
}
if (n._item !== null && n._item.next !== null) {
n = /** @type {Y.ContentType} */
n._item.next.content.type;
} else {
do {
n = n._item === null ? null : n._item.parent;
pos--;
} while (n !== type && n !== null && n._item !== null && n._item.next === null);
if (n !== null && n !== type) {
n = n._item === null ? null : (
/** @type {Y.ContentType} */
/** @type Y.Item */
n._item.next.content.type
);
}
}
} else {
const pNodeSize = (
/** @type {any} */
(mapping.get(n) || { nodeSize: 0 }).nodeSize
);
if (n._first !== null && pos < pNodeSize) {
n = /** @type {Y.ContentType} */
n._first.content.type;
pos--;
} else {
if (pos === 1 && n._length === 0 && pNodeSize > 1) {
return new RelativePosition(n._item === null ? null : n._item.id, n._item === null ? findRootTypeKey(n) : null, null);
}
pos -= pNodeSize;
if (n._item !== null && n._item.next !== null) {
n = /** @type {Y.ContentType} */
n._item.next.content.type;
} else {
if (pos === 0) {
n = n._item === null ? n : n._item.parent;
return new RelativePosition(n._item === null ? null : n._item.id, n._item === null ? findRootTypeKey(n) : null, null);
}
do {
n = /** @type {Y.Item} */
n._item.parent;
pos--;
} while (n !== type && /** @type {Y.Item} */
n._item.next === null);
if (n !== type) {
n = /** @type {Y.ContentType} */
/** @type {Y.Item} */
/** @type {Y.Item} */
n._item.next.content.type;
}
}
}
}
if (n === null) {
throw unexpectedCase();
}
if (pos === 0 && n.constructor !== YXmlText && n !== type) {
return createRelativePosition(n._item.parent, n._item);
}
}
return createRelativePositionFromTypeIndex(type, type._length);
};
var createRelativePosition = (type, item) => {
let typeid = null;
let tname = null;
if (type._item === null) {
tname = findRootTypeKey(type);
} else {
typeid = createID(type._item.id.client, type._item.id.clock);
}
return new RelativePosition(typeid, tname, item.id);
};
var relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) => {
const decodedPos = createAbsolutePositionFromRelativePosition(relPos, y);
if (decodedPos === null || decodedPos.type !== documentType && !isParentOf(documentType, decodedPos.type._item)) {
return null;
}
let type = decodedPos.type;
let pos = 0;
if (type.constructor === YXmlText) {
pos = decodedPos.index;
} else if (type._item === null || !type._item.deleted) {
let n = type._first;
let i = 0;
while (i < type._length && i < decodedPos.index && n !== null) {
if (!n.deleted) {
const t = (
/** @type {Y.ContentType} */
n.content.type
);
i++;
if (t instanceof YXmlText) {
pos += t._length;
} else {
pos += /** @type {any} */
mapping.get(t).nodeSize;
}
}
n = /** @type {Y.Item} */
n.right;
}
pos += 1;
}
while (type !== documentType && type._item !== null) {
const parent = type._item.parent;
if (parent._item === null || !parent._item.deleted) {
pos += 1;
let n = (
/** @type {Y.AbstractType} */
parent._first
);
while (n !== null) {
const contentType = (
/** @type {Y.ContentType} */
n.content.type
);
if (contentType === type) {
break;
}
if (!n.deleted) {
if (contentType instanceof YXmlText) {
pos += contentType._length;
} else {
pos += /** @type {any} */
mapping.get(contentType).nodeSize;
}
}
n = n.right;
}
}
type = /** @type {Y.AbstractType} */
parent;
}
return pos - 1;
};
// node_modules/y-prosemirror/src/plugins/undo-plugin.js
var undo = (state) => {
const undoManager = yUndoPluginKey.getState(state).undoManager;
if (undoManager != null) {
undoManager.undo();
return true;
}
};
var redo = (state) => {
const undoManager = yUndoPluginKey.getState(state).undoManager;
if (undoManager != null) {
undoManager.redo();
return true;
}
};
var defaultProtectedNodes = /* @__PURE__ */ new Set(["paragraph"]);
var defaultDeleteFilter = (item, protectedNodes) => !(item instanceof Item) || !(item.content instanceof ContentType) || !(item.content.type instanceof YText || item.content.type instanceof YXmlElement && protectedNodes.has(item.content.type.nodeName)) || item.content.type._length === 0;
var yUndoPlugin = ({ protectedNodes = defaultProtectedNodes, trackedOrigins = [], undoManager = null } = {}) => new Plugin({
key: yUndoPluginKey,
state: {
init: (initargs, state) => {
const ystate = ySyncPluginKey.getState(state);
const _undoManager = undoManager || new UndoManager(ystate.type, {
trackedOrigins: new Set([ySyncPluginKey].concat(trackedOrigins)),
deleteFilter: (item) => defaultDeleteFilter(item, protectedNodes),
captureTransaction: (tr) => tr.meta.get("addToHistory") !== false
});
return {
undoManager: _undoManager,
prevSel: null,
hasUndoOps: _undoManager.undoStack.length > 0,
hasRedoOps: _undoManager.redoStack.length > 0
};
},
/**
* @returns {any}
*/
apply: (tr, val, oldState, state) => {
const binding = ySyncPluginKey.getState(state).binding;
const undoManager2 = val.undoManager;
const hasUndoOps = undoManager2.undoStack.length > 0;
const hasRedoOps = undoManager2.redoStack.length > 0;
if (binding) {
return {
undoManager: undoManager2,
prevSel: getRelativeSelection(binding, oldState),
hasUndoOps,
hasRedoOps
};
} else {
if (hasUndoOps !== val.hasUndoOps || hasRedoOps !== val.hasRedoOps) {
return Object.assign({}, val, {
hasUndoOps: undoManager2.undoStack.length > 0,
hasRedoOps: undoManager2.redoStack.length > 0
});
} else {
return val;
}
}
}
},
view: (view) => {
const ystate = ySyncPluginKey.getState(view.state);
const undoManager2 = yUndoPluginKey.getState(view.state).undoManager;
undoManager2.on("stack-item-added", ({ stackItem }) => {
const binding = ystate.binding;
if (binding) {
stackItem.meta.set(binding, yUndoPluginKey.getState(view.state).prevSel);
}
});
undoManager2.on("stack-item-popped", ({ stackItem }) => {
const binding = ystate.binding;
if (binding) {
binding.beforeTransactionSelection = stackItem.meta.get(binding) || binding.beforeTransactionSelection;
}
});
return {
destroy: () => {
undoManager2.destroy();
}
};
}
});
// node_modules/@tiptap/extension-collaboration/dist/index.js
var Collaboration = Extension.create({
name: "collaboration",
priority: 1e3,
addOptions() {
return {
document: null,
field: "default",
fragment: null
};
},
onCreate() {
if (this.editor.extensionManager.extensions.find((extension) => extension.name === "history")) {
console.warn('[tiptap warn]: "@tiptap/extension-collaboration" comes with its own history support and is not compatible with "@tiptap/extension-history".');
}
},
addCommands() {
return {
undo: () => ({ tr, state, dispatch }) => {
tr.setMeta("preventDispatch", true);
const undoManager = yUndoPluginKey.getState(state).undoManager;
if (undoManager.undoStack.length === 0) {
return false;
}
if (!dispatch) {
return true;
}
return undo(state);
},
redo: () => ({ tr, state, dispatch }) => {
tr.setMeta("preventDispatch", true);
const undoManager = yUndoPluginKey.getState(state).undoManager;
if (undoManager.redoStack.length === 0) {
return false;
}
if (!dispatch) {
return true;
}
return redo(state);
}
};
},
addKeyboardShortcuts() {
return {
"Mod-z": () => this.editor.commands.undo(),
"Mod-y": () => this.editor.commands.redo(),
"Shift-Mod-z": () => this.editor.commands.redo()
};
},
addProseMirrorPlugins() {
const fragment = this.options.fragment ? this.options.fragment : this.options.document.getXmlFragment(this.options.field);
const yUndoPluginInstance = yUndoPlugin();
const originalUndoPluginView = yUndoPluginInstance.spec.view;
yUndoPluginInstance.spec.view = (view) => {
const { undoManager } = yUndoPluginKey.getState(view.state);
if (undoManager.restore) {
undoManager.restore();
undoManager.restore = () => {
};
}
const viewRet = originalUndoPluginView(view);
return {
destroy: () => {
const hasUndoManSelf = undoManager.trackedOrigins.has(undoManager);
const observers = undoManager._observers;
undoManager.restore = () => {
if (hasUndoManSelf) {
undoManager.trackedOrigins.add(undoManager);
}
undoManager.doc.on("afterTransaction", undoManager.afterTransactionHandler);
undoManager._observers = observers;
};
viewRet.destroy();
}
};
};
return [ySyncPlugin(fragment), yUndoPluginInstance];
}
});
// src/input/richtext/RichTextField.browser.tsx
import { createId, Field, Type } from "alinea/core";
import { entries } from "alinea/core/util/Objects";
import { FormRow } from "alinea/dashboard/atoms/FormAtoms";
import { InputForm } from "alinea/dashboard/editor/InputForm";
import { useField, useFieldMutator } from "alinea/dashboard/editor/UseField";
import { IconButton } from "alinea/dashboard/view/IconButton";
import { InputLabel } from "alinea/dashboard/view/InputLabel";
import { fromModule, HStack, Icon, px, TextLabel } from "alinea/ui";
import { DropdownMenu } from "alinea/ui/DropdownMenu";
import IcRoundAddCircle from "alinea/ui/icons/IcRoundAddCircle";
import { IcRoundClose } from "alinea/ui/icons/IcRoundClose";
import { IcRoundDragHandle } from "alinea/ui/icons/IcRoundDragHandle";
import { IcRoundNotes } from "alinea/ui/icons/IcRoundNotes";
import { Sink } from "alinea/ui/Sink";
import {
createContext,
useCallback,
useContext,
useMemo,
useRef,
useState
} from "react";
import { useEditor } from "./hook/UseEditor.js";
import { PickTextLink, usePickTextLink } from "./PickTextLink.js";
import { richText as createRichText } from "./RichTextField.js";
// src/input/richtext/RichTextField.module.scss
var RichTextField_module_default = {
"hyphenate": "alinea-RichTextField-hyphenate",
"link": "alinea-RichTextField-link",
"p": "alinea-RichTextField-p",
"h5": "alinea-RichTextField-h5",
"h4": "alinea-RichTextField-h4",
"h3": "alinea-RichTextField-h3",
"h2": "alinea-RichTextField-h2",
"h1": "alinea-RichTextField-h1",
"is-flat": "alinea-RichTextField-is-flat",
"isFlat": "alinea-RichTextField-is-flat",
"monospace": "alinea-RichTextField-monospace",
"small": "alinea-RichTextField-small",
"root-editor": "alinea-RichTextField-editor",
"rootEditor": "alinea-RichTextField-editor",
"is-focus": "alinea-RichTextField-is-focus",
"isFocus": "alinea-RichTextField-is-focus",
"is-readonly": "alinea-RichTextField-is-readonly",
"isReadonly": "alinea-RichTextField-is-readonly",
"insert-trigger": "alinea-RichTextField-insert-trigger",
"insertTrigger": "alinea-RichTextField-insert-trigger"
};
// src/input/richtext/RichTextField.browser.tsx
import { RichTextKit } from "./RichTextKit.js";
import { RichTextToolbar } from "./RichTextToolbar.js";
export * from "./RichTextField.js";
import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
var richText = Field.provideView(RichTextInput, createRichText);
var styles = fromModule(RichTextField_module_default);
var IsNested = createContext(false);
function typeExtension(field, name, type) {
function View({ node, deleteNode }) {
const { id } = node.attrs;
const meta = Type.meta(type);
return /* @__PURE__ */ jsx(FormRow, { field, type, rowId: id, children: /* @__PURE__ */ jsx(NodeViewWrapper, { children: /* @__PURE__ */ jsxs(Sink.Root, { style: { margin: `${px(18)} 0` }, children: [
/* @__PURE__ */ jsxs(Sink.Header, { children: [
/* @__PURE__ */ jsx(Sink.Options, { children: /* @__PURE__ */ jsx(
IconButton,
{
icon: meta.icon || IcRoundDragHandle,
"data-drag-handle": true,
style: { cursor: "grab" }
}
) }),
/* @__PURE__ */ jsx(Sink.Title, { children: /* @__PURE__ */ jsx(TextLabel, { label: Type.label(type) }) }),
/* @__PURE__ */ jsx(Sink.Options, { children: /* @__PURE__ */ jsx(IconButton, { icon: IcRoundClose, onClick: deleteNode }) })
] }),
/* @__PURE__ */ jsx(Sink.Content, { children: /* @__PURE__ */ jsx(IsNested.Provider, { value: true, children: /* @__PURE__ */ jsx(InputForm, { type }) }) })
] }) }) });
}
return Node.create({
name,
group: "block",
atom: true,
draggable: true,
parseHTML() {
return [{ tag: name }];
},
renderHTML({ HTMLAttributes }) {
return [name, mergeAttributes(HTMLAttributes)];
},
addNodeView() {
return ReactNodeViewRenderer(View);
},
addAttributes() {
return {
id: { default: null }
};
}
});
}
function schemaToExtensions(field, schema) {
if (!schema)
return [];
return entries(schema).map(([name, type]) => {
return typeExtension(field, name, type);
});
}
function InsertMenu({ editor, schema, onInsert }) {
const id = createId();
if (!schema)
return null;
const blocks = entries(schema).map(([key, type]) => {
return /* @__PURE__ */ jsx(
DropdownMenu.Item,
{
onClick: () => {
onInsert(id, key);
editor.chain().focus().insertContent({ type: key, attrs: { id } }).run();
},
children: /* @__PURE__ */ jsxs(HStack, { center: true, gap: 8, children: [
/* @__PURE__ */ jsx(Icon, { icon: Type.meta(type).icon ?? IcRoundAddCircle }),
/* @__PURE__ */ jsx(TextLabel, { label: Type.label(type) })
] })
},
key
);
});
return /* @__PURE__ */ jsx(
FloatingMenu,
{
editor,
tippyOptions: {
zIndex: 1,
maxWidth: "none"
},
children: /* @__PURE__ */ jsxs(DropdownMenu.Root, { bottom: true, children: [
/* @__PURE__ */ jsxs(DropdownMenu.Trigger, { className: styles.insert.trigger(), children: [
/* @__PURE__ */ jsx(Icon, { icon: IcRoundAddCircle }),
/* @__PURE__ */ jsx("span", { children: "Insert block" })
] }),
/* @__PURE__ */ jsx(DropdownMenu.Items, { children: blocks })
] })
}
);
}
function RichTextEditor({
field
}) {
const { value, mutator, label, options } = useField(field);
const { readOnly, fragment, insert } = mutator;
const picker = usePickTextLink();
const { optional, inline, help, width, schema } = options;
const [focus, setFocus] = useState(false);
const toolbarRef = useRef(null);
const containerRef = useRef(null);
const focusToggle = useCallback(
function focusToggle2(target) {
const element = target || document.activeElement;
const editorElement = () => containerRef.current?.querySelector(
`${styles.root.editor.toSelector()} > .ProseMirror`
);
const isFocused = toolbarRef.current?.contains(element) || element === editorElement() || false;
setFocus(isFocused);
},
[setFocus, containerRef, toolbarRef]
);
const extensions = [
Collaboration.configure({ fragment }),
RichTextKit,
...schemaToExtensions(field, schema)
];
const isNested = useContext(IsNested);
const content = useMemo(
() => ({
type: "doc",
content: value.map((node) => {
if (node.type === "text")
return node;
const { type, ...attrs } = node;
if (schema?.[type])
return { type, attrs: { id: node.id } };
return {
type,
content: "content" in node ? node.content : void 0,
attrs
};
})
}),
[]
);
const onFocus = useCallback(
({ event }) => focusToggle(event.currentTarget),
[focusToggle]
);
const onBlur = useCallback(
({ event }) => focusToggle(event.relatedTarget),
[focusToggle]
);
const editor = useEditor(
{
content: isNested ? void 0 : content,
onFocus,
onBlur,
extensions,
editable: !options.readOnly && !readOnly
},
[]
);
if (!editor)
return null;
return /* @__PURE__ */ jsxs(Fragment2, { children: [
focus && /* @__PURE__ */ jsx(
RichTextToolbar,
{
ref: toolbarRef,
editor,
focusToggle,
pickLink: picker.pickLink
}
),
/* @__PURE__ */ jsx(PickTextLink, { picker }),
/* @__PURE__ */ jsxs(
InputLabel,
{
label,
help,
optional,
inline,
width,
focused: focus,
icon: IcRoundNotes,
empty: editor.isEmpty,
ref: containerRef,
children: [
/* @__PURE__ */ jsx(InsertMenu, { editor, schema, onInsert: insert }),
/* @__PURE__ */ jsx(
EditorContent,
{
className: styles.root.editor({ focus, readonly: options.readOnly }),
editor
}
)
]
}
)
] });
}
function RichTextInput({
field
}) {
const { fragment } = useFieldMutator(field);
const key = useMemo(createId, [fragment]);
return /* @__PURE__ */ jsx(RichTextEditor, { field }, key);
}
export {
RichTextInput,
richText
};