@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
3 lines (2 loc) • 16.7 kB
JavaScript
"use strict";var B=Object.defineProperty;var O=(r,a,e)=>a in r?B(r,a,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[a]=e;var c=(r,a,e)=>O(r,typeof a!="symbol"?a+"":a,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const x=require("prosemirror-state"),I=require("prosemirror-view"),R=require("y-prosemirror"),U=require("./BlockNoteExtension-BWw0r8Gy.cjs"),P=require("./ShowSelection-BW37oJ6h.cjs"),Y=require("@tiptap/core"),z=require("./EventEmitter-CLwfmbqG.cjs"),N=require("yjs"),S=require("uuid");function L(r){if(r&&typeof r=="object"&&"default"in r)return r;const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const e in r)if(e!=="default"){const t=Object.getOwnPropertyDescriptor(r,e);Object.defineProperty(a,e,t.get?t:{enumerable:!0,get:()=>r[e]})}}return a.default=r,Object.freeze(a)}const g=L(N),E=Y.Mark.create({name:"comment",excludes:"",inclusive:!1,keepOnSplit:!0,addAttributes(){return{orphan:{parseHTML:r=>!!r.getAttribute("data-orphan"),renderHTML:r=>r.orphan?{"data-orphan":"true"}:{},default:!1},threadId:{parseHTML:r=>r.getAttribute("data-bn-thread-id"),renderHTML:r=>({"data-bn-thread-id":r.threadId}),default:""}}},renderHTML({HTMLAttributes:r}){return["span",Y.mergeAttributes(r,{class:"bn-thread-mark"})]},parseHTML(){return[{tag:"span.bn-thread-mark"}]},extendMarkSchema(r){return r.name==="comment"?{blocknoteIgnore:!0}:{}}});class _ extends z.EventEmitter{constructor(e){super();c(this,"userCache",new Map);c(this,"loadingUsers",new Set);this.resolveUsers=e}async loadUsers(e){const t=e.filter(d=>!this.userCache.has(d)&&!this.loadingUsers.has(d));if(t.length!==0){for(const d of t)this.loadingUsers.add(d);try{const d=await this.resolveUsers(t);for(const s of d)this.userCache.set(s.id,s);this.emit("update",this.userCache)}finally{for(const d of t)this.loadingUsers.delete(d)}}}getUser(e){return this.userCache.get(e)}subscribe(e){return this.on("update",e)}}const y=new x.PluginKey("blocknote-comments");function H(r,a){const e=new Map;return r.descendants((t,d)=>{t.marks.forEach(s=>{if(s.type.name===a){const o=s.attrs.threadId;if(!o)return;const m=d,n=m+t.nodeSize,i=e.get(o)??{from:1/0,to:0};e.set(o,{from:Math.min(m,i.from),to:Math.max(n,i.to)})}})}),e}const F=U.createExtension(({editor:r,options:{schema:a,threadStore:e,resolveUsers:t}})=>{if(!t)throw new Error("resolveUsers is required to be defined when using comments");if(!e)throw new Error("threadStore is required to be defined when using comments");const d=E.name,s=new _(t),o=U.createStore({pendingComment:!1,selectedThreadId:void 0,threadPositions:new Map},{onUpdate(){o.state.selectedThreadId!==o.prevState.selectedThreadId&&r.transact(n=>n.setMeta(y,!0))}}),m=n=>{r.transact(i=>{i.doc.descendants((u,l)=>{u.marks.forEach(h=>{if(h.type.name===d){const T=h.type,f=h.attrs.threadId,v=n.get(f),C=!!(!v||v.resolved||v.deletedAt);if(C!==h.attrs.orphan){const M=Math.max(l,0),j=Math.min(l+u.nodeSize,i.doc.content.size-1,i.doc.content.size-1);i.removeMark(M,j,h),i.addMark(M,j,T.create({...h.attrs,orphan:C})),C&&o.state.selectedThreadId===f&&o.setState(k=>({...k,selectedThreadId:void 0}))}}})})})};return{key:"comments",store:o,prosemirrorPlugins:[new x.Plugin({key:y,state:{init(){return{decorations:I.DecorationSet.empty}},apply(n,i){const u=n.getMeta(y);if(!n.docChanged&&!u)return i;const l=n.docChanged?H(n.doc,d):o.state.threadPositions;(l.size>0||o.state.threadPositions.size>0)&&o.setState(T=>({...T,threadPositions:l}));const h=[];if(o.state.selectedThreadId){const T=l.get(o.state.selectedThreadId);T&&h.push(I.Decoration.inline(T.from,T.to,{class:"bn-thread-mark-selected"}))}return{decorations:I.DecorationSet.create(n.doc,h)}}},props:{decorations(n){var i;return((i=y.getState(n))==null?void 0:i.decorations)??I.DecorationSet.empty},handleClick:(n,i,u)=>{if(u.button!==0)return;const l=n.state.doc.nodeAt(i);if(!l){o.setState(f=>({...f,selectedThreadId:void 0}));return}const h=l.marks.find(f=>f.type.name===d&&f.attrs.orphan!==!0),T=h==null?void 0:h.attrs.threadId;T!==o.state.selectedThreadId&&o.setState(f=>({...f,selectedThreadId:T}))}}})],threadStore:e,mount(){const n=e.subscribe(m);m(e.getThreads());const i=r.onSelectionChange(()=>{o.state.pendingComment&&o.setState(u=>({...u,pendingComment:!1}))});return()=>{n(),i()}},selectThread(n,i=!0){var u,l;if(o.state.selectedThreadId!==n&&(o.setState(h=>({...h,pendingComment:!1,selectedThreadId:n})),n&&i)){const h=o.state.threadPositions.get(n);if(!h)return;(l=(u=r.prosemirrorView)==null?void 0:u.domAtPos(h.from).node)==null||l.scrollIntoView({behavior:"smooth",block:"center"})}},startPendingComment(){var n;o.setState(i=>({...i,selectedThreadId:void 0,pendingComment:!0})),(n=r.getExtension(P.ShowSelectionExtension))==null||n.showSelection(!0)},stopPendingComment(){var n;o.setState(i=>({...i,selectedThreadId:void 0,pendingComment:!1})),(n=r.getExtension(P.ShowSelectionExtension))==null||n.showSelection(!1)},async createThread(n){const i=await e.createThread(n);if(e.addThreadToDocument){const u=r.prosemirrorView,l=u.state.selection,h=R.ySyncPluginKey.getState(u.state),T={prosemirror:{head:l.head,anchor:l.anchor},yjs:h?R.getRelativeSelection(h.binding,u.state):void 0};await e.addThreadToDocument({threadId:i.id,selection:T})}else r._tiptapEditor.commands.setMark(d,{orphan:!1,threadId:i.id})},userStore:s,commentEditorSchema:a,tiptapExtensions:[E]}});class ${}class V extends ${constructor(a,e){super(),this.userId=a,this.role=e}canCreateThread(){return!0}canAddComment(a){return!0}canUpdateComment(a){return a.userId===this.userId}canDeleteComment(a){return a.userId===this.userId||this.role==="editor"}canDeleteThread(a){return this.role==="editor"}canResolveThread(a){return!0}canUnresolveThread(a){return!0}canAddReaction(a,e){return e?!a.reactions.some(t=>t.emoji===e&&t.userIds.includes(this.userId)):!0}canDeleteReaction(a,e){return e?a.reactions.some(t=>t.emoji===e&&t.userIds.includes(this.userId)):!0}}class b{constructor(a){c(this,"auth");this.auth=a}}class K extends b{constructor(e,t,d){super(d);c(this,"addThreadToDocument");this.userId=e,this.provider=t}async createThread(e){let t=this.provider.createThread({data:e.metadata});return t=this.provider.addComment(t.id,{content:e.initialComment.body,data:{metadata:e.initialComment.metadata,userId:this.userId}}),this.tiptapThreadToThreadData(t)}async addComment(e){const t=this.provider.addComment(e.threadId,{content:e.comment.body,data:{metadata:e.comment.metadata,userId:this.userId}});return this.tiptapCommentToCommentData(t.comments[t.comments.length-1])}async updateComment(e){const t=this.provider.getThreadComment(e.threadId,e.commentId,!0);if(!t)throw new Error("Comment not found");this.provider.updateComment(e.threadId,e.commentId,{content:e.comment.body,data:{...t.data,metadata:e.comment.metadata}})}tiptapCommentToCommentData(e){var d,s,o;const t=[];for(const m of((d=e.data)==null?void 0:d.reactions)||[]){const n=t.find(i=>i.emoji===m.emoji);n?(n.userIds.push(m.userId),n.createdAt=new Date(Math.min(n.createdAt.getTime(),m.createdAt))):t.push({emoji:m.emoji,createdAt:new Date(m.createdAt),userIds:[m.userId]})}return{type:"comment",id:e.id,body:e.content,metadata:(s=e.data)==null?void 0:s.metadata,userId:(o=e.data)==null?void 0:o.userId,createdAt:new Date(e.createdAt),updatedAt:new Date(e.updatedAt),reactions:t}}tiptapThreadToThreadData(e){var t;return{type:"thread",id:e.id,comments:e.comments.map(d=>this.tiptapCommentToCommentData(d)),resolved:!!e.resolvedAt,metadata:(t=e.data)==null?void 0:t.metadata,createdAt:new Date(e.createdAt),updatedAt:new Date(e.updatedAt)}}async deleteComment(e){this.provider.deleteComment(e.threadId,e.commentId)}async deleteThread(e){this.provider.deleteThread(e.threadId)}async resolveThread(e){this.provider.updateThread(e.threadId,{resolvedAt:new Date().toISOString()})}async unresolveThread(e){this.provider.updateThread(e.threadId,{resolvedAt:null})}async addReaction(e){var d;const t=this.provider.getThreadComment(e.threadId,e.commentId,!0);if(!t)throw new Error("Comment not found");this.provider.updateComment(e.threadId,e.commentId,{data:{...t.data,reactions:[...((d=t.data)==null?void 0:d.reactions)||[],{emoji:e.emoji,createdAt:Date.now(),userId:this.userId}]}})}async deleteReaction(e){var d;const t=this.provider.getThreadComment(e.threadId,e.commentId,!0);if(!t)throw new Error("Comment not found");this.provider.updateComment(e.threadId,e.commentId,{data:{...t.data,reactions:(((d=t.data)==null?void 0:d.reactions)||[]).filter(s=>s.emoji!==e.emoji&&s.userId!==this.userId)}})}getThread(e){const t=this.provider.getThread(e);if(!t)throw new Error("Thread not found");return this.tiptapThreadToThreadData(t)}getThreads(){return new Map(this.provider.getThreads().map(e=>[e.id,this.tiptapThreadToThreadData(e)]))}subscribe(e){const t=()=>{e(this.getThreads())};return this.provider.watchThreads(t),()=>{this.provider.unwatchThreads(t)}}}function q(r){const a=new g.Map;if(a.set("id",r.id),a.set("userId",r.userId),a.set("createdAt",r.createdAt.getTime()),a.set("updatedAt",r.updatedAt.getTime()),r.deletedAt?(a.set("deletedAt",r.deletedAt.getTime()),a.set("body",void 0)):a.set("body",r.body),r.reactions.length>0)throw new Error("Reactions should be empty in commentToYMap");return a.set("reactionsByUser",new g.Map),a.set("metadata",r.metadata),a}function G(r){var t;const a=new g.Map;a.set("id",r.id),a.set("createdAt",r.createdAt.getTime()),a.set("updatedAt",r.updatedAt.getTime());const e=new g.Array;return e.push(r.comments.map(d=>q(d))),a.set("comments",e),a.set("resolved",r.resolved),a.set("resolvedUpdatedAt",(t=r.resolvedUpdatedAt)==null?void 0:t.getTime()),a.set("resolvedBy",r.resolvedBy),a.set("metadata",r.metadata),a}function J(r){return{emoji:r.get("emoji"),createdAt:new Date(r.get("createdAt")),userId:r.get("userId")}}function Q(r){return[...r.values()].map(e=>J(e)).reduce((e,t)=>{const d=e.find(s=>s.emoji===t.emoji);return d?(d.userIds.push(t.userId),d.createdAt=new Date(Math.min(d.createdAt.getTime(),t.createdAt.getTime()))):e.push({emoji:t.emoji,createdAt:t.createdAt,userIds:[t.userId]}),e},[])}function w(r){return{type:"comment",id:r.get("id"),userId:r.get("userId"),createdAt:new Date(r.get("createdAt")),updatedAt:new Date(r.get("updatedAt")),deletedAt:r.get("deletedAt")?new Date(r.get("deletedAt")):void 0,reactions:Q(r.get("reactionsByUser")),metadata:r.get("metadata"),body:r.get("body")}}function p(r){return{type:"thread",id:r.get("id"),createdAt:new Date(r.get("createdAt")),updatedAt:new Date(r.get("updatedAt")),comments:(r.get("comments")||[]).map(a=>w(a)),resolved:r.get("resolved"),resolvedUpdatedAt:new Date(r.get("resolvedUpdatedAt")),resolvedBy:r.get("resolvedBy"),metadata:r.get("metadata")}}class D extends b{constructor(a,e){super(e),this.threadsYMap=a}getThread(a){const e=this.threadsYMap.get(a);if(!e)throw new Error("Thread not found");return p(e)}getThreads(){const a=new Map;return this.threadsYMap.forEach((e,t)=>{e instanceof g.Map&&a.set(t,p(e))}),a}subscribe(a){const e=()=>{a(this.getThreads())};return this.threadsYMap.observeDeep(e),()=>{this.threadsYMap.unobserveDeep(e)}}}class W extends D{constructor(e,t,d,s){super(d,s);c(this,"doRequest",async(e,t,d)=>{const s=await fetch(`${this.BASE_URL}${e}`,{method:t,body:JSON.stringify(d),headers:{"Content-Type":"application/json",...this.headers}});if(!s.ok)throw new Error(`Failed to ${t} ${e}: ${s.statusText}`);return s.json()});c(this,"addThreadToDocument",async e=>{const{threadId:t,...d}=e;return this.doRequest(`/${t}/addToDocument`,"POST",d)});c(this,"createThread",async e=>this.doRequest("","POST",e));c(this,"addComment",e=>{const{threadId:t,...d}=e;return this.doRequest(`/${t}/comments`,"POST",d)});c(this,"updateComment",e=>{const{threadId:t,commentId:d,...s}=e;return this.doRequest(`/${t}/comments/${d}`,"PUT",s)});c(this,"deleteComment",e=>{const{threadId:t,commentId:d,...s}=e;return this.doRequest(`/${t}/comments/${d}?soft=${!!s.softDelete}`,"DELETE")});c(this,"deleteThread",e=>this.doRequest(`/${e.threadId}`,"DELETE"));c(this,"resolveThread",e=>this.doRequest(`/${e.threadId}/resolve`,"POST"));c(this,"unresolveThread",e=>this.doRequest(`/${e.threadId}/unresolve`,"POST"));c(this,"addReaction",e=>{const{threadId:t,commentId:d,...s}=e;return this.doRequest(`/${t}/comments/${d}/reactions`,"POST",s)});c(this,"deleteReaction",e=>this.doRequest(`/${e.threadId}/comments/${e.commentId}/reactions/${e.emoji}`,"DELETE"));this.BASE_URL=e,this.headers=t}}class X extends D{constructor(e,t,d){super(t,d);c(this,"transact",e=>async t=>this.threadsYMap.doc.transact(()=>e(t)));c(this,"createThread",this.transact(e=>{if(!this.auth.canCreateThread())throw new Error("Not authorized");const t=new Date,d={type:"comment",id:S.v4(),userId:this.userId,createdAt:t,updatedAt:t,reactions:[],metadata:e.initialComment.metadata,body:e.initialComment.body},s={type:"thread",id:S.v4(),createdAt:t,updatedAt:t,comments:[d],resolved:!1,metadata:e.metadata};return this.threadsYMap.set(s.id,G(s)),s}));c(this,"addThreadToDocument");c(this,"addComment",this.transact(e=>{const t=this.threadsYMap.get(e.threadId);if(!t)throw new Error("Thread not found");if(!this.auth.canAddComment(p(t)))throw new Error("Not authorized");const d=new Date,s={type:"comment",id:S.v4(),userId:this.userId,createdAt:d,updatedAt:d,deletedAt:void 0,reactions:[],metadata:e.comment.metadata,body:e.comment.body};return t.get("comments").push([q(s)]),t.set("updatedAt",new Date().getTime()),s}));c(this,"updateComment",this.transact(e=>{const t=this.threadsYMap.get(e.threadId);if(!t)throw new Error("Thread not found");const d=A(t.get("comments"),o=>o.get("id")===e.commentId);if(d===-1)throw new Error("Comment not found");const s=t.get("comments").get(d);if(!this.auth.canUpdateComment(w(s)))throw new Error("Not authorized");s.set("body",e.comment.body),s.set("updatedAt",new Date().getTime()),s.set("metadata",e.comment.metadata)}));c(this,"deleteComment",this.transact(e=>{const t=this.threadsYMap.get(e.threadId);if(!t)throw new Error("Thread not found");const d=A(t.get("comments"),o=>o.get("id")===e.commentId);if(d===-1)throw new Error("Comment not found");const s=t.get("comments").get(d);if(!this.auth.canDeleteComment(w(s)))throw new Error("Not authorized");if(s.get("deletedAt"))throw new Error("Comment already deleted");e.softDelete?(s.set("deletedAt",new Date().getTime()),s.set("body",void 0)):t.get("comments").delete(d),t.get("comments").toArray().every(o=>o.get("deletedAt"))&&(e.softDelete?t.set("deletedAt",new Date().getTime()):this.threadsYMap.delete(e.threadId)),t.set("updatedAt",new Date().getTime())}));c(this,"deleteThread",this.transact(e=>{if(!this.auth.canDeleteThread(p(this.threadsYMap.get(e.threadId))))throw new Error("Not authorized");this.threadsYMap.delete(e.threadId)}));c(this,"resolveThread",this.transact(e=>{const t=this.threadsYMap.get(e.threadId);if(!t)throw new Error("Thread not found");if(!this.auth.canResolveThread(p(t)))throw new Error("Not authorized");t.set("resolved",!0),t.set("resolvedUpdatedAt",new Date().getTime()),t.set("resolvedBy",this.userId)}));c(this,"unresolveThread",this.transact(e=>{const t=this.threadsYMap.get(e.threadId);if(!t)throw new Error("Thread not found");if(!this.auth.canUnresolveThread(p(t)))throw new Error("Not authorized");t.set("resolved",!1),t.set("resolvedUpdatedAt",new Date().getTime())}));c(this,"addReaction",this.transact(e=>{const t=this.threadsYMap.get(e.threadId);if(!t)throw new Error("Thread not found");const d=A(t.get("comments"),i=>i.get("id")===e.commentId);if(d===-1)throw new Error("Comment not found");const s=t.get("comments").get(d);if(!this.auth.canAddReaction(w(s),e.emoji))throw new Error("Not authorized");const o=new Date,m=`${this.userId}-${e.emoji}`,n=s.get("reactionsByUser");if(!n.has(m)){const i=new g.Map;i.set("emoji",e.emoji),i.set("createdAt",o.getTime()),i.set("userId",this.userId),n.set(m,i)}}));c(this,"deleteReaction",this.transact(e=>{const t=this.threadsYMap.get(e.threadId);if(!t)throw new Error("Thread not found");const d=A(t.get("comments"),n=>n.get("id")===e.commentId);if(d===-1)throw new Error("Comment not found");const s=t.get("comments").get(d);if(!this.auth.canDeleteReaction(w(s),e.emoji))throw new Error("Not authorized");const o=`${this.userId}-${e.emoji}`;s.get("reactionsByUser").delete(o)}));this.userId=e}}function A(r,a){for(let e=0;e<r.length;e++)if(a(r.get(e)))return e;return-1}exports.CommentMark=E;exports.CommentsExtension=F;exports.DefaultThreadStoreAuth=V;exports.RESTYjsThreadStore=W;exports.ThreadStore=b;exports.ThreadStoreAuth=$;exports.TiptapThreadStore=K;exports.YjsThreadStore=X;exports.YjsThreadStoreBase=D;
//# sourceMappingURL=comments.cjs.map