@embedpdf/plugin-history 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@embedpdf/core"),e="history",o={id:e,name:"History Plugin",version:"1.0.0",provides:["history"],requires:[],optional:[],defaultConfig:{}},n="HISTORY/INIT_STATE",i="HISTORY/CLEANUP_STATE",s="HISTORY/SET_DOCUMENT_STATE",c=class extends t.BasePlugin{constructor(e,o){super(e,o),this.documentHistories=new Map,this.historyChange$=t.createEmitter()}async initialize(t){this.logger.info("HistoryPlugin","Initialize","History plugin initialized")}onDocumentLoadingStarted(t){this.dispatch((t=>({type:n,payload:{documentId:t}}))(t)),this.documentHistories.set(t,{topicHistories:new Map,globalTimeline:[],globalIndex:-1}),this.logger.debug("HistoryPlugin","DocumentOpened",`Initialized history state for document: ${t}`)}onDocumentClosed(t){this.dispatch((t=>({type:i,payload:{documentId:t}}))(t)),this.documentHistories.delete(t),this.logger.debug("HistoryPlugin","DocumentClosed",`Cleaned up history state for document: ${t}`)}getDocumentHistoryData(t){const e=t??this.getActiveDocumentId(),o=this.documentHistories.get(e);if(!o)throw new Error(`History data not found for document: ${e}`);return o}getDocumentHistoryState(t){const e=this.documentHistories.get(t);if(!e)throw new Error(`History data not found for document: ${t}`);const o={};return Array.from(e.topicHistories.entries()).forEach(([t,e])=>{o[t]={canUndo:e.currentIndex>-1,canRedo:e.currentIndex<e.commands.length-1}}),{global:{canUndo:e.globalIndex>-1,canRedo:e.globalIndex<e.globalTimeline.length-1},topics:o}}emitHistoryChange(t,e){const o=this.getDocumentHistoryState(t);this.dispatch(((t,e)=>({type:s,payload:{documentId:t,state:e}}))(t,o)),this.historyChange$.emit({documentId:t,topic:e,state:o})}register(t,e,o){const n=this.getDocumentHistoryData(o);n.topicHistories.has(e)||n.topicHistories.set(e,{commands:[],currentIndex:-1});const i=n.topicHistories.get(e);i.commands.splice(i.currentIndex+1),i.commands.push(t),i.currentIndex++;const s={command:t,topic:e};n.globalTimeline.splice(n.globalIndex+1),n.globalTimeline.push(s),n.globalIndex++,t.execute(),this.emitHistoryChange(o,e)}undo(t,e){const o=this.getDocumentHistoryData(e);let n;if(t){const e=o.topicHistories.get(t);e&&e.currentIndex>-1&&(e.commands[e.currentIndex].undo(),e.currentIndex--,n=t)}else if(o.globalIndex>-1){const t=o.globalTimeline[o.globalIndex];t.command.undo(),o.topicHistories.get(t.topic).currentIndex--,o.globalIndex--,n=t.topic}n&&this.emitHistoryChange(e,n)}redo(t,e){const o=this.getDocumentHistoryData(e);let n;if(t){const e=o.topicHistories.get(t);e&&e.currentIndex<e.commands.length-1&&(e.currentIndex++,e.commands[e.currentIndex].execute(),n=t)}else if(o.globalIndex<o.globalTimeline.length-1){o.globalIndex++;const t=o.globalTimeline[o.globalIndex];t.command.execute(),o.topicHistories.get(t.topic).currentIndex++,n=t.topic}n&&this.emitHistoryChange(e,n)}canUndo(t,e){const o=this.getDocumentHistoryData(e);if(t){const e=o.topicHistories.get(t);return!!e&&e.currentIndex>-1}return o.globalIndex>-1}canRedo(t,e){const o=this.getDocumentHistoryData(e);if(t){const e=o.topicHistories.get(t);return!!e&&e.currentIndex<e.commands.length-1}return o.globalIndex<o.globalTimeline.length-1}createHistoryScope(t){return{register:(e,o)=>this.register(e,o,t),undo:e=>this.undo(e,t),redo:e=>this.redo(e,t),canUndo:e=>this.canUndo(e,t),canRedo:e=>this.canRedo(e,t),getHistoryState:()=>this.getDocumentHistoryState(t),onHistoryChange:e=>this.historyChange$.on(o=>{o.documentId===t&&e(o.topic)})}}buildCapability(){return{register:(t,e)=>{const o=this.getActiveDocumentId();this.register(t,e,o)},undo:t=>{const e=this.getActiveDocumentId();this.undo(t,e)},redo:t=>{const e=this.getActiveDocumentId();this.redo(t,e)},canUndo:t=>{const e=this.getActiveDocumentId();return this.canUndo(t,e)},canRedo:t=>{const e=this.getActiveDocumentId();return this.canRedo(t,e)},getHistoryState:()=>{const t=this.getActiveDocumentId();return this.getDocumentHistoryState(t)},forDocument:t=>this.createHistoryScope(t),onHistoryChange:this.historyChange$.on}}async destroy(){this.historyChange$.clear(),this.documentHistories.clear(),super.destroy()}};c.id="history";let r=c;const d={global:{canUndo:!1,canRedo:!1},topics:{}},a={documents:{},activeDocumentId:null},u={manifest:o,create:t=>new r(e,t),reducer:(t=a,e)=>{switch(e.type){case n:{const{documentId:o}=e.payload;return{...t,documents:{...t.documents,[o]:{...d}}}}case i:{const{documentId:o}=e.payload,{[o]:n,...i}=t.documents;return{...t,documents:i,activeDocumentId:t.activeDocumentId===o?null:t.activeDocumentId}}case s:{const{documentId:o,state:n}=e.payload;return t.documents[o]?{...t,documents:{...t.documents,[o]:n}}:t}case"HISTORY/SET_ACTIVE_DOCUMENT":return{...t,activeDocumentId:e.payload};default:return t}},initialState:a};exports.HISTORY_PLUGIN_ID=e,exports.HistoryPlugin=r,exports.HistoryPluginPackage=u,exports.manifest=o;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@embedpdf/core"),e="history",o={id:e,name:"History Plugin",version:"1.0.0",provides:["history"],requires:[],optional:[],defaultConfig:{}},n="HISTORY/INIT_STATE",i="HISTORY/CLEANUP_STATE",s="HISTORY/SET_DOCUMENT_STATE",r=class extends t.BasePlugin{constructor(e,o){super(e,o),this.documentHistories=new Map,this.historyChange$=t.createEmitter()}async initialize(t){this.logger.info("HistoryPlugin","Initialize","History plugin initialized")}onDocumentLoadingStarted(t){this.dispatch((t=>({type:n,payload:{documentId:t}}))(t)),this.documentHistories.set(t,{topicHistories:new Map,globalTimeline:[],globalIndex:-1}),this.logger.debug("HistoryPlugin","DocumentOpened",`Initialized history state for document: ${t}`)}onDocumentClosed(t){this.dispatch((t=>({type:i,payload:{documentId:t}}))(t)),this.documentHistories.delete(t),this.logger.debug("HistoryPlugin","DocumentClosed",`Cleaned up history state for document: ${t}`)}getDocumentHistoryData(t){const e=t??this.getActiveDocumentId(),o=this.documentHistories.get(e);if(!o)throw new Error(`History data not found for document: ${e}`);return o}getDocumentHistoryState(t){const e=this.documentHistories.get(t);if(!e)throw new Error(`History data not found for document: ${t}`);const o={};return Array.from(e.topicHistories.entries()).forEach(([t,e])=>{o[t]={canUndo:e.currentIndex>-1,canRedo:e.currentIndex<e.commands.length-1}}),{global:{canUndo:e.globalIndex>-1,canRedo:e.globalIndex<e.globalTimeline.length-1},topics:o}}emitHistoryChange(t,e){const o=this.getDocumentHistoryState(t);this.dispatch(((t,e)=>({type:s,payload:{documentId:t,state:e}}))(t,o)),this.historyChange$.emit({documentId:t,topic:e,state:o})}register(t,e,o){const n=this.getDocumentHistoryData(o);n.topicHistories.has(e)||n.topicHistories.set(e,{commands:[],currentIndex:-1});const i=n.topicHistories.get(e);i.commands.splice(i.currentIndex+1),i.commands.push(t),i.currentIndex++;const s={command:t,topic:e};n.globalTimeline.splice(n.globalIndex+1),n.globalTimeline.push(s),n.globalIndex++,t.execute(),this.emitHistoryChange(o,e)}undo(t,e){const o=this.getDocumentHistoryData(e);let n;if(t){const e=o.topicHistories.get(t);e&&e.currentIndex>-1&&(e.commands[e.currentIndex].undo(),e.currentIndex--,n=t)}else if(o.globalIndex>-1){const t=o.globalTimeline[o.globalIndex];t.command.undo(),o.topicHistories.get(t.topic).currentIndex--,o.globalIndex--,n=t.topic}n&&this.emitHistoryChange(e,n)}redo(t,e){const o=this.getDocumentHistoryData(e);let n;if(t){const e=o.topicHistories.get(t);e&&e.currentIndex<e.commands.length-1&&(e.currentIndex++,e.commands[e.currentIndex].execute(),n=t)}else if(o.globalIndex<o.globalTimeline.length-1){o.globalIndex++;const t=o.globalTimeline[o.globalIndex];t.command.execute(),o.topicHistories.get(t.topic).currentIndex++,n=t.topic}n&&this.emitHistoryChange(e,n)}canUndo(t,e){const o=this.getDocumentHistoryData(e);if(t){const e=o.topicHistories.get(t);return!!e&&e.currentIndex>-1}return o.globalIndex>-1}canRedo(t,e){const o=this.getDocumentHistoryData(e);if(t){const e=o.topicHistories.get(t);return!!e&&e.currentIndex<e.commands.length-1}return o.globalIndex<o.globalTimeline.length-1}purgeByMetadata(t,e,o){const n=this.getDocumentHistoryData(o);let i=0;const s=e=>t(e.metadata),r=e?[e]:Array.from(n.topicHistories.keys());for(const d of r){const t=n.topicHistories.get(d);if(!t)continue;const e=[];let o=0;for(let n=0;n<t.commands.length;n++){const r=t.commands[n];s(r)?(n<=t.currentIndex&&o++,i++):e.push(r)}t.commands=e,t.currentIndex=Math.max(-1,t.currentIndex-o)}const c=[];let a=0;for(let d=0;d<n.globalTimeline.length;d++){const t=n.globalTimeline[d];(!e||t.topic===e)&&s(t.command)?d<=n.globalIndex&&a++:c.push(t)}return n.globalTimeline=c,n.globalIndex=Math.max(-1,n.globalIndex-a),i>0&&(this.emitHistoryChange(o,e),this.logger.debug("HistoryPlugin","PurgeByMetadata",`Purged ${i} history entries for document: ${o}${e?`, topic: ${e}`:""}`)),i}createHistoryScope(t){return{register:(e,o)=>this.register(e,o,t),undo:e=>this.undo(e,t),redo:e=>this.redo(e,t),canUndo:e=>this.canUndo(e,t),canRedo:e=>this.canRedo(e,t),getHistoryState:()=>this.getDocumentHistoryState(t),onHistoryChange:e=>this.historyChange$.on(o=>{o.documentId===t&&e(o.topic)}),purgeByMetadata:(e,o)=>this.purgeByMetadata(e,o,t)}}buildCapability(){return{register:(t,e)=>{const o=this.getActiveDocumentId();this.register(t,e,o)},undo:t=>{const e=this.getActiveDocumentId();this.undo(t,e)},redo:t=>{const e=this.getActiveDocumentId();this.redo(t,e)},canUndo:t=>{const e=this.getActiveDocumentId();return this.canUndo(t,e)},canRedo:t=>{const e=this.getActiveDocumentId();return this.canRedo(t,e)},getHistoryState:()=>{const t=this.getActiveDocumentId();return this.getDocumentHistoryState(t)},forDocument:t=>this.createHistoryScope(t),onHistoryChange:this.historyChange$.on,purgeByMetadata:(t,e)=>{const o=this.getActiveDocumentId();return this.purgeByMetadata(t,e,o)}}}async destroy(){this.historyChange$.clear(),this.documentHistories.clear(),super.destroy()}};r.id="history";let c=r;const a={global:{canUndo:!1,canRedo:!1},topics:{}},d={documents:{},activeDocumentId:null},u={manifest:o,create:t=>new c(e,t),reducer:(t=d,e)=>{switch(e.type){case n:{const{documentId:o}=e.payload;return{...t,documents:{...t.documents,[o]:{...a}}}}case i:{const{documentId:o}=e.payload,{[o]:n,...i}=t.documents;return{...t,documents:i,activeDocumentId:t.activeDocumentId===o?null:t.activeDocumentId}}case s:{const{documentId:o,state:n}=e.payload;return t.documents[o]?{...t,documents:{...t.documents,[o]:n}}:t}case"HISTORY/SET_ACTIVE_DOCUMENT":return{...t,activeDocumentId:e.payload};default:return t}},initialState:d};exports.HISTORY_PLUGIN_ID=e,exports.HistoryPlugin=c,exports.HistoryPluginPackage=u,exports.manifest=o;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/lib/manifest.ts","../src/lib/actions.ts","../src/lib/history-plugin.ts","../src/lib/reducer.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { HistoryPluginConfig } from './types';\n\nexport const HISTORY_PLUGIN_ID = 'history';\n\nexport const manifest: PluginManifest<HistoryPluginConfig> = {\n id: HISTORY_PLUGIN_ID,\n name: 'History Plugin',\n version: '1.0.0',\n provides: ['history'],\n requires: [],\n optional: [],\n defaultConfig: {},\n};\n","import { Action } from '@embedpdf/core';\nimport { HistoryDocumentState } from './types';\n\n// Document lifecycle actions\nexport const INIT_HISTORY_STATE = 'HISTORY/INIT_STATE';\nexport const CLEANUP_HISTORY_STATE = 'HISTORY/CLEANUP_STATE';\n\n// History state updates\nexport const SET_HISTORY_DOCUMENT_STATE = 'HISTORY/SET_DOCUMENT_STATE';\nexport const SET_ACTIVE_HISTORY_DOCUMENT = 'HISTORY/SET_ACTIVE_DOCUMENT';\n\n// Document lifecycle action interfaces\nexport interface InitHistoryStateAction extends Action {\n type: typeof INIT_HISTORY_STATE;\n payload: {\n documentId: string;\n };\n}\n\nexport interface CleanupHistoryStateAction extends Action {\n type: typeof CLEANUP_HISTORY_STATE;\n payload: {\n documentId: string;\n };\n}\n\n// State update action interfaces\nexport interface SetHistoryDocumentStateAction extends Action {\n type: typeof SET_HISTORY_DOCUMENT_STATE;\n payload: {\n documentId: string;\n state: HistoryDocumentState;\n };\n}\n\nexport interface SetActiveHistoryDocumentAction extends Action {\n type: typeof SET_ACTIVE_HISTORY_DOCUMENT;\n payload: string | null; // documentId\n}\n\nexport type HistoryAction =\n | InitHistoryStateAction\n | CleanupHistoryStateAction\n | SetHistoryDocumentStateAction\n | SetActiveHistoryDocumentAction;\n\n// Action creators\nexport const initHistoryState = (documentId: string): InitHistoryStateAction => ({\n type: INIT_HISTORY_STATE,\n payload: { documentId },\n});\n\nexport const cleanupHistoryState = (documentId: string): CleanupHistoryStateAction => ({\n type: CLEANUP_HISTORY_STATE,\n payload: { documentId },\n});\n\nexport const setHistoryDocumentState = (\n documentId: string,\n state: HistoryDocumentState,\n): SetHistoryDocumentStateAction => ({\n type: SET_HISTORY_DOCUMENT_STATE,\n payload: { documentId, state },\n});\n\nexport const setActiveHistoryDocument = (\n documentId: string | null,\n): SetActiveHistoryDocumentAction => ({\n type: SET_ACTIVE_HISTORY_DOCUMENT,\n payload: documentId,\n});\n","import { BasePlugin, createEmitter, Listener, PluginRegistry } from '@embedpdf/core';\nimport {\n Command,\n HistoryCapability,\n HistoryChangeEvent,\n HistoryDocumentState,\n HistoryEntry,\n HistoryPluginConfig,\n HistoryScope,\n HistoryState,\n} from './types';\nimport {\n HistoryAction,\n initHistoryState,\n cleanupHistoryState,\n setHistoryDocumentState,\n} from './actions';\n\ninterface DocumentHistoryData {\n topicHistories: Map<string, { commands: Command[]; currentIndex: number }>;\n globalTimeline: HistoryEntry[];\n globalIndex: number;\n}\n\nexport class HistoryPlugin extends BasePlugin<\n HistoryPluginConfig,\n HistoryCapability,\n HistoryState,\n HistoryAction\n> {\n static readonly id = 'history' as const;\n\n // Per-document history data (persisted with state)\n private readonly documentHistories = new Map<string, DocumentHistoryData>();\n\n // Event emitter for history changes\n private readonly historyChange$ = createEmitter<HistoryChangeEvent>();\n\n constructor(id: string, registry: PluginRegistry) {\n super(id, registry);\n }\n\n async initialize(_: HistoryPluginConfig): Promise<void> {\n this.logger.info('HistoryPlugin', 'Initialize', 'History plugin initialized');\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Lifecycle (from BasePlugin)\n // ─────────────────────────────────────────────────────────\n\n protected override onDocumentLoadingStarted(documentId: string): void {\n // Initialize history state for this document\n this.dispatch(initHistoryState(documentId));\n\n // Create document history data\n this.documentHistories.set(documentId, {\n topicHistories: new Map(),\n globalTimeline: [],\n globalIndex: -1,\n });\n\n this.logger.debug(\n 'HistoryPlugin',\n 'DocumentOpened',\n `Initialized history state for document: ${documentId}`,\n );\n }\n\n protected override onDocumentClosed(documentId: string): void {\n // Cleanup history state\n this.dispatch(cleanupHistoryState(documentId));\n\n // Cleanup document history data\n this.documentHistories.delete(documentId);\n\n this.logger.debug(\n 'HistoryPlugin',\n 'DocumentClosed',\n `Cleaned up history state for document: ${documentId}`,\n );\n }\n\n // ─────────────────────────────────────────────────────────\n // Helper Methods\n // ─────────────────────────────────────────────────────────\n\n private getDocumentHistoryData(documentId?: string): DocumentHistoryData {\n const id = documentId ?? this.getActiveDocumentId();\n const data = this.documentHistories.get(id);\n if (!data) {\n throw new Error(`History data not found for document: ${id}`);\n }\n return data;\n }\n\n private getDocumentHistoryState(documentId: string): HistoryDocumentState {\n const data = this.documentHistories.get(documentId);\n if (!data) {\n throw new Error(`History data not found for document: ${documentId}`);\n }\n\n const topics: HistoryDocumentState['topics'] = {};\n Array.from(data.topicHistories.entries()).forEach(([topic, history]) => {\n topics[topic] = {\n canUndo: history.currentIndex > -1,\n canRedo: history.currentIndex < history.commands.length - 1,\n };\n });\n\n return {\n global: {\n canUndo: data.globalIndex > -1,\n canRedo: data.globalIndex < data.globalTimeline.length - 1,\n },\n topics,\n };\n }\n\n private emitHistoryChange(documentId: string, topic: string | undefined) {\n // Update the state\n const state = this.getDocumentHistoryState(documentId);\n this.dispatch(setHistoryDocumentState(documentId, state));\n\n // Emit the event\n this.historyChange$.emit({\n documentId,\n topic,\n state,\n });\n }\n\n // ─────────────────────────────────────────────────────────\n // History Operations (per document)\n // ─────────────────────────────────────────────────────────\n\n private register(command: Command, topic: string, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n\n // 1. Manage Topic History\n if (!data.topicHistories.has(topic)) {\n data.topicHistories.set(topic, { commands: [], currentIndex: -1 });\n }\n const topicHistory = data.topicHistories.get(topic)!;\n topicHistory.commands.splice(topicHistory.currentIndex + 1);\n topicHistory.commands.push(command);\n topicHistory.currentIndex++;\n\n // 2. Manage Global History\n const historyEntry: HistoryEntry = { command, topic };\n data.globalTimeline.splice(data.globalIndex + 1);\n data.globalTimeline.push(historyEntry);\n data.globalIndex++;\n\n // 3. Execute and notify with the specific topic\n command.execute();\n this.emitHistoryChange(documentId, topic);\n }\n\n private undo(topic: string | undefined, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n let affectedTopic: string | undefined;\n\n if (topic) {\n // Scoped Undo\n const topicHistory = data.topicHistories.get(topic);\n if (topicHistory && topicHistory.currentIndex > -1) {\n topicHistory.commands[topicHistory.currentIndex].undo();\n topicHistory.currentIndex--;\n affectedTopic = topic;\n }\n } else {\n // Global Undo\n if (data.globalIndex > -1) {\n const entry = data.globalTimeline[data.globalIndex];\n entry.command.undo();\n data.topicHistories.get(entry.topic)!.currentIndex--;\n data.globalIndex--;\n affectedTopic = entry.topic;\n }\n }\n\n if (affectedTopic) {\n this.emitHistoryChange(documentId, affectedTopic);\n }\n }\n\n private redo(topic: string | undefined, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n let affectedTopic: string | undefined;\n\n if (topic) {\n // Scoped Redo\n const topicHistory = data.topicHistories.get(topic);\n if (topicHistory && topicHistory.currentIndex < topicHistory.commands.length - 1) {\n topicHistory.currentIndex++;\n topicHistory.commands[topicHistory.currentIndex].execute();\n affectedTopic = topic;\n }\n } else {\n // Global Redo\n if (data.globalIndex < data.globalTimeline.length - 1) {\n data.globalIndex++;\n const entry = data.globalTimeline[data.globalIndex];\n entry.command.execute();\n data.topicHistories.get(entry.topic)!.currentIndex++;\n affectedTopic = entry.topic;\n }\n }\n\n if (affectedTopic) {\n this.emitHistoryChange(documentId, affectedTopic);\n }\n }\n\n private canUndo(topic: string | undefined, documentId: string): boolean {\n const data = this.getDocumentHistoryData(documentId);\n\n if (topic) {\n const history = data.topicHistories.get(topic);\n return !!history && history.currentIndex > -1;\n }\n return data.globalIndex > -1;\n }\n\n private canRedo(topic: string | undefined, documentId: string): boolean {\n const data = this.getDocumentHistoryData(documentId);\n\n if (topic) {\n const history = data.topicHistories.get(topic);\n return !!history && history.currentIndex < history.commands.length - 1;\n }\n return data.globalIndex < data.globalTimeline.length - 1;\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Scoping\n // ─────────────────────────────────────────────────────────\n\n private createHistoryScope(documentId: string): HistoryScope {\n return {\n register: (command: Command, topic: string) => this.register(command, topic, documentId),\n undo: (topic?: string) => this.undo(topic, documentId),\n redo: (topic?: string) => this.redo(topic, documentId),\n canUndo: (topic?: string) => this.canUndo(topic, documentId),\n canRedo: (topic?: string) => this.canRedo(topic, documentId),\n getHistoryState: () => this.getDocumentHistoryState(documentId),\n onHistoryChange: (listener: Listener<string | undefined>) =>\n this.historyChange$.on((event) => {\n if (event.documentId === documentId) {\n listener(event.topic);\n }\n }),\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Capability\n // ─────────────────────────────────────────────────────────\n\n protected buildCapability(): HistoryCapability {\n return {\n // Active document operations\n register: (command: Command, topic: string) => {\n const documentId = this.getActiveDocumentId();\n this.register(command, topic, documentId);\n },\n\n undo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n this.undo(topic, documentId);\n },\n\n redo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n this.redo(topic, documentId);\n },\n\n canUndo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.canUndo(topic, documentId);\n },\n\n canRedo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.canRedo(topic, documentId);\n },\n\n getHistoryState: () => {\n const documentId = this.getActiveDocumentId();\n return this.getDocumentHistoryState(documentId);\n },\n\n // Document-scoped operations\n forDocument: (documentId: string) => this.createHistoryScope(documentId),\n\n // Events\n onHistoryChange: this.historyChange$.on,\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Lifecycle\n // ─────────────────────────────────────────────────────────\n\n async destroy(): Promise<void> {\n // Clear all emitters\n this.historyChange$.clear();\n\n // Clear document histories\n this.documentHistories.clear();\n\n super.destroy();\n }\n}\n","import { Reducer } from '@embedpdf/core';\nimport {\n HistoryAction,\n INIT_HISTORY_STATE,\n CLEANUP_HISTORY_STATE,\n SET_HISTORY_DOCUMENT_STATE,\n SET_ACTIVE_HISTORY_DOCUMENT,\n} from './actions';\nimport { HistoryState, HistoryDocumentState } from './types';\n\nconst initialDocumentState: HistoryDocumentState = {\n global: {\n canUndo: false,\n canRedo: false,\n },\n topics: {},\n};\n\nexport const initialState: HistoryState = {\n documents: {},\n activeDocumentId: null,\n};\n\nexport const reducer: Reducer<HistoryState, HistoryAction> = (state = initialState, action) => {\n switch (action.type) {\n case INIT_HISTORY_STATE: {\n const { documentId } = action.payload;\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: { ...initialDocumentState },\n },\n };\n }\n\n case CLEANUP_HISTORY_STATE: {\n const { documentId } = action.payload;\n const { [documentId]: removed, ...remainingDocs } = state.documents;\n\n return {\n ...state,\n documents: remainingDocs,\n activeDocumentId: state.activeDocumentId === documentId ? null : state.activeDocumentId,\n };\n }\n\n case SET_HISTORY_DOCUMENT_STATE: {\n const { documentId, state: docState } = action.payload;\n if (!state.documents[documentId]) {\n return state;\n }\n\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: docState,\n },\n };\n }\n\n case SET_ACTIVE_HISTORY_DOCUMENT: {\n return {\n ...state,\n activeDocumentId: action.payload,\n };\n }\n\n default:\n return state;\n }\n};\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, HISTORY_PLUGIN_ID } from './manifest';\nimport { HistoryPluginConfig, HistoryState } from './types';\nimport { HistoryPlugin } from './history-plugin';\nimport { initialState, reducer } from './reducer';\nimport { HistoryAction } from './actions';\n\nexport const HistoryPluginPackage: PluginPackage<\n HistoryPlugin,\n HistoryPluginConfig,\n HistoryState,\n HistoryAction\n> = {\n manifest,\n create: (registry) => new HistoryPlugin(HISTORY_PLUGIN_ID, registry),\n reducer,\n initialState,\n};\n\nexport * from './history-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":["HISTORY_PLUGIN_ID","manifest","id","name","version","provides","requires","optional","defaultConfig","INIT_HISTORY_STATE","CLEANUP_HISTORY_STATE","SET_HISTORY_DOCUMENT_STATE","_HistoryPlugin","BasePlugin","constructor","registry","super","this","documentHistories","Map","historyChange$","createEmitter","initialize","_","logger","info","onDocumentLoadingStarted","documentId","dispatch","type","payload","initHistoryState","set","topicHistories","globalTimeline","globalIndex","debug","onDocumentClosed","cleanupHistoryState","delete","getDocumentHistoryData","getActiveDocumentId","data","get","Error","getDocumentHistoryState","topics","Array","from","entries","forEach","topic","history","canUndo","currentIndex","canRedo","commands","length","global","emitHistoryChange","state","setHistoryDocumentState","emit","register","command","has","topicHistory","splice","push","historyEntry","execute","undo","affectedTopic","entry","redo","createHistoryScope","getHistoryState","onHistoryChange","listener","on","event","buildCapability","forDocument","destroy","clear","HistoryPlugin","initialDocumentState","initialState","documents","activeDocumentId","HistoryPluginPackage","create","reducer","action","removed","remainingDocs","docState"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/lib/manifest.ts","../src/lib/actions.ts","../src/lib/history-plugin.ts","../src/lib/reducer.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { HistoryPluginConfig } from './types';\n\nexport const HISTORY_PLUGIN_ID = 'history';\n\nexport const manifest: PluginManifest<HistoryPluginConfig> = {\n id: HISTORY_PLUGIN_ID,\n name: 'History Plugin',\n version: '1.0.0',\n provides: ['history'],\n requires: [],\n optional: [],\n defaultConfig: {},\n};\n","import { Action } from '@embedpdf/core';\nimport { HistoryDocumentState } from './types';\n\n// Document lifecycle actions\nexport const INIT_HISTORY_STATE = 'HISTORY/INIT_STATE';\nexport const CLEANUP_HISTORY_STATE = 'HISTORY/CLEANUP_STATE';\n\n// History state updates\nexport const SET_HISTORY_DOCUMENT_STATE = 'HISTORY/SET_DOCUMENT_STATE';\nexport const SET_ACTIVE_HISTORY_DOCUMENT = 'HISTORY/SET_ACTIVE_DOCUMENT';\n\n// Document lifecycle action interfaces\nexport interface InitHistoryStateAction extends Action {\n type: typeof INIT_HISTORY_STATE;\n payload: {\n documentId: string;\n };\n}\n\nexport interface CleanupHistoryStateAction extends Action {\n type: typeof CLEANUP_HISTORY_STATE;\n payload: {\n documentId: string;\n };\n}\n\n// State update action interfaces\nexport interface SetHistoryDocumentStateAction extends Action {\n type: typeof SET_HISTORY_DOCUMENT_STATE;\n payload: {\n documentId: string;\n state: HistoryDocumentState;\n };\n}\n\nexport interface SetActiveHistoryDocumentAction extends Action {\n type: typeof SET_ACTIVE_HISTORY_DOCUMENT;\n payload: string | null; // documentId\n}\n\nexport type HistoryAction =\n | InitHistoryStateAction\n | CleanupHistoryStateAction\n | SetHistoryDocumentStateAction\n | SetActiveHistoryDocumentAction;\n\n// Action creators\nexport const initHistoryState = (documentId: string): InitHistoryStateAction => ({\n type: INIT_HISTORY_STATE,\n payload: { documentId },\n});\n\nexport const cleanupHistoryState = (documentId: string): CleanupHistoryStateAction => ({\n type: CLEANUP_HISTORY_STATE,\n payload: { documentId },\n});\n\nexport const setHistoryDocumentState = (\n documentId: string,\n state: HistoryDocumentState,\n): SetHistoryDocumentStateAction => ({\n type: SET_HISTORY_DOCUMENT_STATE,\n payload: { documentId, state },\n});\n\nexport const setActiveHistoryDocument = (\n documentId: string | null,\n): SetActiveHistoryDocumentAction => ({\n type: SET_ACTIVE_HISTORY_DOCUMENT,\n payload: documentId,\n});\n","import { BasePlugin, createEmitter, Listener, PluginRegistry } from '@embedpdf/core';\nimport {\n Command,\n HistoryCapability,\n HistoryChangeEvent,\n HistoryDocumentState,\n HistoryEntry,\n HistoryPluginConfig,\n HistoryScope,\n HistoryState,\n} from './types';\nimport {\n HistoryAction,\n initHistoryState,\n cleanupHistoryState,\n setHistoryDocumentState,\n} from './actions';\n\ninterface DocumentHistoryData {\n topicHistories: Map<string, { commands: Command[]; currentIndex: number }>;\n globalTimeline: HistoryEntry[];\n globalIndex: number;\n}\n\nexport class HistoryPlugin extends BasePlugin<\n HistoryPluginConfig,\n HistoryCapability,\n HistoryState,\n HistoryAction\n> {\n static readonly id = 'history' as const;\n\n // Per-document history data (persisted with state)\n private readonly documentHistories = new Map<string, DocumentHistoryData>();\n\n // Event emitter for history changes\n private readonly historyChange$ = createEmitter<HistoryChangeEvent>();\n\n constructor(id: string, registry: PluginRegistry) {\n super(id, registry);\n }\n\n async initialize(_: HistoryPluginConfig): Promise<void> {\n this.logger.info('HistoryPlugin', 'Initialize', 'History plugin initialized');\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Lifecycle (from BasePlugin)\n // ─────────────────────────────────────────────────────────\n\n protected override onDocumentLoadingStarted(documentId: string): void {\n // Initialize history state for this document\n this.dispatch(initHistoryState(documentId));\n\n // Create document history data\n this.documentHistories.set(documentId, {\n topicHistories: new Map(),\n globalTimeline: [],\n globalIndex: -1,\n });\n\n this.logger.debug(\n 'HistoryPlugin',\n 'DocumentOpened',\n `Initialized history state for document: ${documentId}`,\n );\n }\n\n protected override onDocumentClosed(documentId: string): void {\n // Cleanup history state\n this.dispatch(cleanupHistoryState(documentId));\n\n // Cleanup document history data\n this.documentHistories.delete(documentId);\n\n this.logger.debug(\n 'HistoryPlugin',\n 'DocumentClosed',\n `Cleaned up history state for document: ${documentId}`,\n );\n }\n\n // ─────────────────────────────────────────────────────────\n // Helper Methods\n // ─────────────────────────────────────────────────────────\n\n private getDocumentHistoryData(documentId?: string): DocumentHistoryData {\n const id = documentId ?? this.getActiveDocumentId();\n const data = this.documentHistories.get(id);\n if (!data) {\n throw new Error(`History data not found for document: ${id}`);\n }\n return data;\n }\n\n private getDocumentHistoryState(documentId: string): HistoryDocumentState {\n const data = this.documentHistories.get(documentId);\n if (!data) {\n throw new Error(`History data not found for document: ${documentId}`);\n }\n\n const topics: HistoryDocumentState['topics'] = {};\n Array.from(data.topicHistories.entries()).forEach(([topic, history]) => {\n topics[topic] = {\n canUndo: history.currentIndex > -1,\n canRedo: history.currentIndex < history.commands.length - 1,\n };\n });\n\n return {\n global: {\n canUndo: data.globalIndex > -1,\n canRedo: data.globalIndex < data.globalTimeline.length - 1,\n },\n topics,\n };\n }\n\n private emitHistoryChange(documentId: string, topic: string | undefined) {\n // Update the state\n const state = this.getDocumentHistoryState(documentId);\n this.dispatch(setHistoryDocumentState(documentId, state));\n\n // Emit the event\n this.historyChange$.emit({\n documentId,\n topic,\n state,\n });\n }\n\n // ─────────────────────────────────────────────────────────\n // History Operations (per document)\n // ─────────────────────────────────────────────────────────\n\n private register(command: Command, topic: string, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n\n // 1. Manage Topic History\n if (!data.topicHistories.has(topic)) {\n data.topicHistories.set(topic, { commands: [], currentIndex: -1 });\n }\n const topicHistory = data.topicHistories.get(topic)!;\n topicHistory.commands.splice(topicHistory.currentIndex + 1);\n topicHistory.commands.push(command);\n topicHistory.currentIndex++;\n\n // 2. Manage Global History\n const historyEntry: HistoryEntry = { command, topic };\n data.globalTimeline.splice(data.globalIndex + 1);\n data.globalTimeline.push(historyEntry);\n data.globalIndex++;\n\n // 3. Execute and notify with the specific topic\n command.execute();\n this.emitHistoryChange(documentId, topic);\n }\n\n private undo(topic: string | undefined, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n let affectedTopic: string | undefined;\n\n if (topic) {\n // Scoped Undo\n const topicHistory = data.topicHistories.get(topic);\n if (topicHistory && topicHistory.currentIndex > -1) {\n topicHistory.commands[topicHistory.currentIndex].undo();\n topicHistory.currentIndex--;\n affectedTopic = topic;\n }\n } else {\n // Global Undo\n if (data.globalIndex > -1) {\n const entry = data.globalTimeline[data.globalIndex];\n entry.command.undo();\n data.topicHistories.get(entry.topic)!.currentIndex--;\n data.globalIndex--;\n affectedTopic = entry.topic;\n }\n }\n\n if (affectedTopic) {\n this.emitHistoryChange(documentId, affectedTopic);\n }\n }\n\n private redo(topic: string | undefined, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n let affectedTopic: string | undefined;\n\n if (topic) {\n // Scoped Redo\n const topicHistory = data.topicHistories.get(topic);\n if (topicHistory && topicHistory.currentIndex < topicHistory.commands.length - 1) {\n topicHistory.currentIndex++;\n topicHistory.commands[topicHistory.currentIndex].execute();\n affectedTopic = topic;\n }\n } else {\n // Global Redo\n if (data.globalIndex < data.globalTimeline.length - 1) {\n data.globalIndex++;\n const entry = data.globalTimeline[data.globalIndex];\n entry.command.execute();\n data.topicHistories.get(entry.topic)!.currentIndex++;\n affectedTopic = entry.topic;\n }\n }\n\n if (affectedTopic) {\n this.emitHistoryChange(documentId, affectedTopic);\n }\n }\n\n private canUndo(topic: string | undefined, documentId: string): boolean {\n const data = this.getDocumentHistoryData(documentId);\n\n if (topic) {\n const history = data.topicHistories.get(topic);\n return !!history && history.currentIndex > -1;\n }\n return data.globalIndex > -1;\n }\n\n private canRedo(topic: string | undefined, documentId: string): boolean {\n const data = this.getDocumentHistoryData(documentId);\n\n if (topic) {\n const history = data.topicHistories.get(topic);\n return !!history && history.currentIndex < history.commands.length - 1;\n }\n return data.globalIndex < data.globalTimeline.length - 1;\n }\n\n /**\n * Purges history entries that match the given predicate based on command metadata.\n * Used to remove commands that are no longer valid (e.g., after a permanent redaction commit).\n *\n * @param predicate A function that returns true for commands that should be purged\n * @param topic If provided, only purges entries for that specific topic\n * @param documentId The document to purge history for\n * @returns The number of entries that were purged\n */\n private purgeByMetadata<T>(\n predicate: (metadata: T | undefined) => boolean,\n topic: string | undefined,\n documentId: string,\n ): number {\n const data = this.getDocumentHistoryData(documentId);\n let purgedCount = 0;\n\n // Helper to check if a command should be purged\n const shouldPurge = (command: Command): boolean => {\n return predicate(command.metadata as T | undefined);\n };\n\n // 1. Purge from topic histories\n const topicsToProcess = topic ? [topic] : Array.from(data.topicHistories.keys());\n\n for (const topicName of topicsToProcess) {\n const topicHistory = data.topicHistories.get(topicName);\n if (!topicHistory) continue;\n\n const newCommands: Command[] = [];\n let indexAdjustment = 0;\n\n for (let i = 0; i < topicHistory.commands.length; i++) {\n const command = topicHistory.commands[i];\n if (shouldPurge(command)) {\n // If this entry is at or before currentIndex, we need to adjust\n if (i <= topicHistory.currentIndex) {\n indexAdjustment++;\n }\n purgedCount++;\n } else {\n newCommands.push(command);\n }\n }\n\n topicHistory.commands = newCommands;\n topicHistory.currentIndex = Math.max(-1, topicHistory.currentIndex - indexAdjustment);\n }\n\n // 2. Purge from global timeline\n const newTimeline: HistoryEntry[] = [];\n let globalIndexAdjustment = 0;\n\n for (let i = 0; i < data.globalTimeline.length; i++) {\n const entry = data.globalTimeline[i];\n const matchesTopic = !topic || entry.topic === topic;\n\n if (matchesTopic && shouldPurge(entry.command)) {\n // If this entry is at or before globalIndex, we need to adjust\n if (i <= data.globalIndex) {\n globalIndexAdjustment++;\n }\n } else {\n newTimeline.push(entry);\n }\n }\n\n data.globalTimeline = newTimeline;\n data.globalIndex = Math.max(-1, data.globalIndex - globalIndexAdjustment);\n\n // 3. Emit history change if anything was purged\n if (purgedCount > 0) {\n this.emitHistoryChange(documentId, topic);\n this.logger.debug(\n 'HistoryPlugin',\n 'PurgeByMetadata',\n `Purged ${purgedCount} history entries for document: ${documentId}${topic ? `, topic: ${topic}` : ''}`,\n );\n }\n\n return purgedCount;\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Scoping\n // ─────────────────────────────────────────────────────────\n\n private createHistoryScope(documentId: string): HistoryScope {\n return {\n register: (command: Command, topic: string) => this.register(command, topic, documentId),\n undo: (topic?: string) => this.undo(topic, documentId),\n redo: (topic?: string) => this.redo(topic, documentId),\n canUndo: (topic?: string) => this.canUndo(topic, documentId),\n canRedo: (topic?: string) => this.canRedo(topic, documentId),\n getHistoryState: () => this.getDocumentHistoryState(documentId),\n onHistoryChange: (listener: Listener<string | undefined>) =>\n this.historyChange$.on((event) => {\n if (event.documentId === documentId) {\n listener(event.topic);\n }\n }),\n purgeByMetadata: <T>(predicate: (metadata: T | undefined) => boolean, topic?: string) =>\n this.purgeByMetadata(predicate, topic, documentId),\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Capability\n // ─────────────────────────────────────────────────────────\n\n protected buildCapability(): HistoryCapability {\n return {\n // Active document operations\n register: (command: Command, topic: string) => {\n const documentId = this.getActiveDocumentId();\n this.register(command, topic, documentId);\n },\n\n undo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n this.undo(topic, documentId);\n },\n\n redo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n this.redo(topic, documentId);\n },\n\n canUndo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.canUndo(topic, documentId);\n },\n\n canRedo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.canRedo(topic, documentId);\n },\n\n getHistoryState: () => {\n const documentId = this.getActiveDocumentId();\n return this.getDocumentHistoryState(documentId);\n },\n\n // Document-scoped operations\n forDocument: (documentId: string) => this.createHistoryScope(documentId),\n\n // Events\n onHistoryChange: this.historyChange$.on,\n\n // Purge operations\n purgeByMetadata: <T>(predicate: (metadata: T | undefined) => boolean, topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.purgeByMetadata(predicate, topic, documentId);\n },\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Lifecycle\n // ─────────────────────────────────────────────────────────\n\n async destroy(): Promise<void> {\n // Clear all emitters\n this.historyChange$.clear();\n\n // Clear document histories\n this.documentHistories.clear();\n\n super.destroy();\n }\n}\n","import { Reducer } from '@embedpdf/core';\nimport {\n HistoryAction,\n INIT_HISTORY_STATE,\n CLEANUP_HISTORY_STATE,\n SET_HISTORY_DOCUMENT_STATE,\n SET_ACTIVE_HISTORY_DOCUMENT,\n} from './actions';\nimport { HistoryState, HistoryDocumentState } from './types';\n\nconst initialDocumentState: HistoryDocumentState = {\n global: {\n canUndo: false,\n canRedo: false,\n },\n topics: {},\n};\n\nexport const initialState: HistoryState = {\n documents: {},\n activeDocumentId: null,\n};\n\nexport const reducer: Reducer<HistoryState, HistoryAction> = (state = initialState, action) => {\n switch (action.type) {\n case INIT_HISTORY_STATE: {\n const { documentId } = action.payload;\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: { ...initialDocumentState },\n },\n };\n }\n\n case CLEANUP_HISTORY_STATE: {\n const { documentId } = action.payload;\n const { [documentId]: removed, ...remainingDocs } = state.documents;\n\n return {\n ...state,\n documents: remainingDocs,\n activeDocumentId: state.activeDocumentId === documentId ? null : state.activeDocumentId,\n };\n }\n\n case SET_HISTORY_DOCUMENT_STATE: {\n const { documentId, state: docState } = action.payload;\n if (!state.documents[documentId]) {\n return state;\n }\n\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: docState,\n },\n };\n }\n\n case SET_ACTIVE_HISTORY_DOCUMENT: {\n return {\n ...state,\n activeDocumentId: action.payload,\n };\n }\n\n default:\n return state;\n }\n};\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, HISTORY_PLUGIN_ID } from './manifest';\nimport { HistoryPluginConfig, HistoryState } from './types';\nimport { HistoryPlugin } from './history-plugin';\nimport { initialState, reducer } from './reducer';\nimport { HistoryAction } from './actions';\n\nexport const HistoryPluginPackage: PluginPackage<\n HistoryPlugin,\n HistoryPluginConfig,\n HistoryState,\n HistoryAction\n> = {\n manifest,\n create: (registry) => new HistoryPlugin(HISTORY_PLUGIN_ID, registry),\n reducer,\n initialState,\n};\n\nexport * from './history-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":["HISTORY_PLUGIN_ID","manifest","id","name","version","provides","requires","optional","defaultConfig","INIT_HISTORY_STATE","CLEANUP_HISTORY_STATE","SET_HISTORY_DOCUMENT_STATE","_HistoryPlugin","BasePlugin","constructor","registry","super","this","documentHistories","Map","historyChange$","createEmitter","initialize","_","logger","info","onDocumentLoadingStarted","documentId","dispatch","type","payload","initHistoryState","set","topicHistories","globalTimeline","globalIndex","debug","onDocumentClosed","cleanupHistoryState","delete","getDocumentHistoryData","getActiveDocumentId","data","get","Error","getDocumentHistoryState","topics","Array","from","entries","forEach","topic","history","canUndo","currentIndex","canRedo","commands","length","global","emitHistoryChange","state","setHistoryDocumentState","emit","register","command","has","topicHistory","splice","push","historyEntry","execute","undo","affectedTopic","entry","redo","purgeByMetadata","predicate","purgedCount","shouldPurge","metadata","topicsToProcess","keys","topicName","newCommands","indexAdjustment","i","Math","max","newTimeline","globalIndexAdjustment","createHistoryScope","getHistoryState","onHistoryChange","listener","on","event","buildCapability","forDocument","destroy","clear","HistoryPlugin","initialDocumentState","initialState","documents","activeDocumentId","HistoryPluginPackage","create","reducer","action","removed","remainingDocs","docState"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -180,6 +180,66 @@ const _HistoryPlugin = class _HistoryPlugin extends BasePlugin {
180
180
  }
181
181
  return data.globalIndex < data.globalTimeline.length - 1;
182
182
  }
183
+ /**
184
+ * Purges history entries that match the given predicate based on command metadata.
185
+ * Used to remove commands that are no longer valid (e.g., after a permanent redaction commit).
186
+ *
187
+ * @param predicate A function that returns true for commands that should be purged
188
+ * @param topic If provided, only purges entries for that specific topic
189
+ * @param documentId The document to purge history for
190
+ * @returns The number of entries that were purged
191
+ */
192
+ purgeByMetadata(predicate, topic, documentId) {
193
+ const data = this.getDocumentHistoryData(documentId);
194
+ let purgedCount = 0;
195
+ const shouldPurge = (command) => {
196
+ return predicate(command.metadata);
197
+ };
198
+ const topicsToProcess = topic ? [topic] : Array.from(data.topicHistories.keys());
199
+ for (const topicName of topicsToProcess) {
200
+ const topicHistory = data.topicHistories.get(topicName);
201
+ if (!topicHistory) continue;
202
+ const newCommands = [];
203
+ let indexAdjustment = 0;
204
+ for (let i = 0; i < topicHistory.commands.length; i++) {
205
+ const command = topicHistory.commands[i];
206
+ if (shouldPurge(command)) {
207
+ if (i <= topicHistory.currentIndex) {
208
+ indexAdjustment++;
209
+ }
210
+ purgedCount++;
211
+ } else {
212
+ newCommands.push(command);
213
+ }
214
+ }
215
+ topicHistory.commands = newCommands;
216
+ topicHistory.currentIndex = Math.max(-1, topicHistory.currentIndex - indexAdjustment);
217
+ }
218
+ const newTimeline = [];
219
+ let globalIndexAdjustment = 0;
220
+ for (let i = 0; i < data.globalTimeline.length; i++) {
221
+ const entry = data.globalTimeline[i];
222
+ const matchesTopic = !topic || entry.topic === topic;
223
+ if (matchesTopic && shouldPurge(entry.command)) {
224
+ if (i <= data.globalIndex) {
225
+ globalIndexAdjustment++;
226
+ }
227
+ } else {
228
+ newTimeline.push(entry);
229
+ }
230
+ }
231
+ data.globalTimeline = newTimeline;
232
+ data.globalIndex = Math.max(-1, data.globalIndex - globalIndexAdjustment);
233
+ if (purgedCount > 0) {
234
+ this.emitHistoryChange(documentId, topic);
235
+ this.logger.debug(
236
+ "HistoryPlugin",
237
+ "PurgeByMetadata",
238
+ `Purged ${purgedCount} history entries for document: ${documentId}${topic ? `, topic: ${topic}` : ""}`
239
+ );
240
+ }
241
+ return purgedCount;
242
+ }
183
243
  // ─────────────────────────────────────────────────────────
184
244
  // Document Scoping
185
245
  // ─────────────────────────────────────────────────────────
@@ -195,7 +255,8 @@ const _HistoryPlugin = class _HistoryPlugin extends BasePlugin {
195
255
  if (event.documentId === documentId) {
196
256
  listener(event.topic);
197
257
  }
198
- })
258
+ }),
259
+ purgeByMetadata: (predicate, topic) => this.purgeByMetadata(predicate, topic, documentId)
199
260
  };
200
261
  }
201
262
  // ─────────────────────────────────────────────────────────
@@ -231,7 +292,12 @@ const _HistoryPlugin = class _HistoryPlugin extends BasePlugin {
231
292
  // Document-scoped operations
232
293
  forDocument: (documentId) => this.createHistoryScope(documentId),
233
294
  // Events
234
- onHistoryChange: this.historyChange$.on
295
+ onHistoryChange: this.historyChange$.on,
296
+ // Purge operations
297
+ purgeByMetadata: (predicate, topic) => {
298
+ const documentId = this.getActiveDocumentId();
299
+ return this.purgeByMetadata(predicate, topic, documentId);
300
+ }
235
301
  };
236
302
  }
237
303
  // ─────────────────────────────────────────────────────────
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/lib/manifest.ts","../src/lib/actions.ts","../src/lib/history-plugin.ts","../src/lib/reducer.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { HistoryPluginConfig } from './types';\n\nexport const HISTORY_PLUGIN_ID = 'history';\n\nexport const manifest: PluginManifest<HistoryPluginConfig> = {\n id: HISTORY_PLUGIN_ID,\n name: 'History Plugin',\n version: '1.0.0',\n provides: ['history'],\n requires: [],\n optional: [],\n defaultConfig: {},\n};\n","import { Action } from '@embedpdf/core';\nimport { HistoryDocumentState } from './types';\n\n// Document lifecycle actions\nexport const INIT_HISTORY_STATE = 'HISTORY/INIT_STATE';\nexport const CLEANUP_HISTORY_STATE = 'HISTORY/CLEANUP_STATE';\n\n// History state updates\nexport const SET_HISTORY_DOCUMENT_STATE = 'HISTORY/SET_DOCUMENT_STATE';\nexport const SET_ACTIVE_HISTORY_DOCUMENT = 'HISTORY/SET_ACTIVE_DOCUMENT';\n\n// Document lifecycle action interfaces\nexport interface InitHistoryStateAction extends Action {\n type: typeof INIT_HISTORY_STATE;\n payload: {\n documentId: string;\n };\n}\n\nexport interface CleanupHistoryStateAction extends Action {\n type: typeof CLEANUP_HISTORY_STATE;\n payload: {\n documentId: string;\n };\n}\n\n// State update action interfaces\nexport interface SetHistoryDocumentStateAction extends Action {\n type: typeof SET_HISTORY_DOCUMENT_STATE;\n payload: {\n documentId: string;\n state: HistoryDocumentState;\n };\n}\n\nexport interface SetActiveHistoryDocumentAction extends Action {\n type: typeof SET_ACTIVE_HISTORY_DOCUMENT;\n payload: string | null; // documentId\n}\n\nexport type HistoryAction =\n | InitHistoryStateAction\n | CleanupHistoryStateAction\n | SetHistoryDocumentStateAction\n | SetActiveHistoryDocumentAction;\n\n// Action creators\nexport const initHistoryState = (documentId: string): InitHistoryStateAction => ({\n type: INIT_HISTORY_STATE,\n payload: { documentId },\n});\n\nexport const cleanupHistoryState = (documentId: string): CleanupHistoryStateAction => ({\n type: CLEANUP_HISTORY_STATE,\n payload: { documentId },\n});\n\nexport const setHistoryDocumentState = (\n documentId: string,\n state: HistoryDocumentState,\n): SetHistoryDocumentStateAction => ({\n type: SET_HISTORY_DOCUMENT_STATE,\n payload: { documentId, state },\n});\n\nexport const setActiveHistoryDocument = (\n documentId: string | null,\n): SetActiveHistoryDocumentAction => ({\n type: SET_ACTIVE_HISTORY_DOCUMENT,\n payload: documentId,\n});\n","import { BasePlugin, createEmitter, Listener, PluginRegistry } from '@embedpdf/core';\nimport {\n Command,\n HistoryCapability,\n HistoryChangeEvent,\n HistoryDocumentState,\n HistoryEntry,\n HistoryPluginConfig,\n HistoryScope,\n HistoryState,\n} from './types';\nimport {\n HistoryAction,\n initHistoryState,\n cleanupHistoryState,\n setHistoryDocumentState,\n} from './actions';\n\ninterface DocumentHistoryData {\n topicHistories: Map<string, { commands: Command[]; currentIndex: number }>;\n globalTimeline: HistoryEntry[];\n globalIndex: number;\n}\n\nexport class HistoryPlugin extends BasePlugin<\n HistoryPluginConfig,\n HistoryCapability,\n HistoryState,\n HistoryAction\n> {\n static readonly id = 'history' as const;\n\n // Per-document history data (persisted with state)\n private readonly documentHistories = new Map<string, DocumentHistoryData>();\n\n // Event emitter for history changes\n private readonly historyChange$ = createEmitter<HistoryChangeEvent>();\n\n constructor(id: string, registry: PluginRegistry) {\n super(id, registry);\n }\n\n async initialize(_: HistoryPluginConfig): Promise<void> {\n this.logger.info('HistoryPlugin', 'Initialize', 'History plugin initialized');\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Lifecycle (from BasePlugin)\n // ─────────────────────────────────────────────────────────\n\n protected override onDocumentLoadingStarted(documentId: string): void {\n // Initialize history state for this document\n this.dispatch(initHistoryState(documentId));\n\n // Create document history data\n this.documentHistories.set(documentId, {\n topicHistories: new Map(),\n globalTimeline: [],\n globalIndex: -1,\n });\n\n this.logger.debug(\n 'HistoryPlugin',\n 'DocumentOpened',\n `Initialized history state for document: ${documentId}`,\n );\n }\n\n protected override onDocumentClosed(documentId: string): void {\n // Cleanup history state\n this.dispatch(cleanupHistoryState(documentId));\n\n // Cleanup document history data\n this.documentHistories.delete(documentId);\n\n this.logger.debug(\n 'HistoryPlugin',\n 'DocumentClosed',\n `Cleaned up history state for document: ${documentId}`,\n );\n }\n\n // ─────────────────────────────────────────────────────────\n // Helper Methods\n // ─────────────────────────────────────────────────────────\n\n private getDocumentHistoryData(documentId?: string): DocumentHistoryData {\n const id = documentId ?? this.getActiveDocumentId();\n const data = this.documentHistories.get(id);\n if (!data) {\n throw new Error(`History data not found for document: ${id}`);\n }\n return data;\n }\n\n private getDocumentHistoryState(documentId: string): HistoryDocumentState {\n const data = this.documentHistories.get(documentId);\n if (!data) {\n throw new Error(`History data not found for document: ${documentId}`);\n }\n\n const topics: HistoryDocumentState['topics'] = {};\n Array.from(data.topicHistories.entries()).forEach(([topic, history]) => {\n topics[topic] = {\n canUndo: history.currentIndex > -1,\n canRedo: history.currentIndex < history.commands.length - 1,\n };\n });\n\n return {\n global: {\n canUndo: data.globalIndex > -1,\n canRedo: data.globalIndex < data.globalTimeline.length - 1,\n },\n topics,\n };\n }\n\n private emitHistoryChange(documentId: string, topic: string | undefined) {\n // Update the state\n const state = this.getDocumentHistoryState(documentId);\n this.dispatch(setHistoryDocumentState(documentId, state));\n\n // Emit the event\n this.historyChange$.emit({\n documentId,\n topic,\n state,\n });\n }\n\n // ─────────────────────────────────────────────────────────\n // History Operations (per document)\n // ─────────────────────────────────────────────────────────\n\n private register(command: Command, topic: string, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n\n // 1. Manage Topic History\n if (!data.topicHistories.has(topic)) {\n data.topicHistories.set(topic, { commands: [], currentIndex: -1 });\n }\n const topicHistory = data.topicHistories.get(topic)!;\n topicHistory.commands.splice(topicHistory.currentIndex + 1);\n topicHistory.commands.push(command);\n topicHistory.currentIndex++;\n\n // 2. Manage Global History\n const historyEntry: HistoryEntry = { command, topic };\n data.globalTimeline.splice(data.globalIndex + 1);\n data.globalTimeline.push(historyEntry);\n data.globalIndex++;\n\n // 3. Execute and notify with the specific topic\n command.execute();\n this.emitHistoryChange(documentId, topic);\n }\n\n private undo(topic: string | undefined, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n let affectedTopic: string | undefined;\n\n if (topic) {\n // Scoped Undo\n const topicHistory = data.topicHistories.get(topic);\n if (topicHistory && topicHistory.currentIndex > -1) {\n topicHistory.commands[topicHistory.currentIndex].undo();\n topicHistory.currentIndex--;\n affectedTopic = topic;\n }\n } else {\n // Global Undo\n if (data.globalIndex > -1) {\n const entry = data.globalTimeline[data.globalIndex];\n entry.command.undo();\n data.topicHistories.get(entry.topic)!.currentIndex--;\n data.globalIndex--;\n affectedTopic = entry.topic;\n }\n }\n\n if (affectedTopic) {\n this.emitHistoryChange(documentId, affectedTopic);\n }\n }\n\n private redo(topic: string | undefined, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n let affectedTopic: string | undefined;\n\n if (topic) {\n // Scoped Redo\n const topicHistory = data.topicHistories.get(topic);\n if (topicHistory && topicHistory.currentIndex < topicHistory.commands.length - 1) {\n topicHistory.currentIndex++;\n topicHistory.commands[topicHistory.currentIndex].execute();\n affectedTopic = topic;\n }\n } else {\n // Global Redo\n if (data.globalIndex < data.globalTimeline.length - 1) {\n data.globalIndex++;\n const entry = data.globalTimeline[data.globalIndex];\n entry.command.execute();\n data.topicHistories.get(entry.topic)!.currentIndex++;\n affectedTopic = entry.topic;\n }\n }\n\n if (affectedTopic) {\n this.emitHistoryChange(documentId, affectedTopic);\n }\n }\n\n private canUndo(topic: string | undefined, documentId: string): boolean {\n const data = this.getDocumentHistoryData(documentId);\n\n if (topic) {\n const history = data.topicHistories.get(topic);\n return !!history && history.currentIndex > -1;\n }\n return data.globalIndex > -1;\n }\n\n private canRedo(topic: string | undefined, documentId: string): boolean {\n const data = this.getDocumentHistoryData(documentId);\n\n if (topic) {\n const history = data.topicHistories.get(topic);\n return !!history && history.currentIndex < history.commands.length - 1;\n }\n return data.globalIndex < data.globalTimeline.length - 1;\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Scoping\n // ─────────────────────────────────────────────────────────\n\n private createHistoryScope(documentId: string): HistoryScope {\n return {\n register: (command: Command, topic: string) => this.register(command, topic, documentId),\n undo: (topic?: string) => this.undo(topic, documentId),\n redo: (topic?: string) => this.redo(topic, documentId),\n canUndo: (topic?: string) => this.canUndo(topic, documentId),\n canRedo: (topic?: string) => this.canRedo(topic, documentId),\n getHistoryState: () => this.getDocumentHistoryState(documentId),\n onHistoryChange: (listener: Listener<string | undefined>) =>\n this.historyChange$.on((event) => {\n if (event.documentId === documentId) {\n listener(event.topic);\n }\n }),\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Capability\n // ─────────────────────────────────────────────────────────\n\n protected buildCapability(): HistoryCapability {\n return {\n // Active document operations\n register: (command: Command, topic: string) => {\n const documentId = this.getActiveDocumentId();\n this.register(command, topic, documentId);\n },\n\n undo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n this.undo(topic, documentId);\n },\n\n redo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n this.redo(topic, documentId);\n },\n\n canUndo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.canUndo(topic, documentId);\n },\n\n canRedo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.canRedo(topic, documentId);\n },\n\n getHistoryState: () => {\n const documentId = this.getActiveDocumentId();\n return this.getDocumentHistoryState(documentId);\n },\n\n // Document-scoped operations\n forDocument: (documentId: string) => this.createHistoryScope(documentId),\n\n // Events\n onHistoryChange: this.historyChange$.on,\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Lifecycle\n // ─────────────────────────────────────────────────────────\n\n async destroy(): Promise<void> {\n // Clear all emitters\n this.historyChange$.clear();\n\n // Clear document histories\n this.documentHistories.clear();\n\n super.destroy();\n }\n}\n","import { Reducer } from '@embedpdf/core';\nimport {\n HistoryAction,\n INIT_HISTORY_STATE,\n CLEANUP_HISTORY_STATE,\n SET_HISTORY_DOCUMENT_STATE,\n SET_ACTIVE_HISTORY_DOCUMENT,\n} from './actions';\nimport { HistoryState, HistoryDocumentState } from './types';\n\nconst initialDocumentState: HistoryDocumentState = {\n global: {\n canUndo: false,\n canRedo: false,\n },\n topics: {},\n};\n\nexport const initialState: HistoryState = {\n documents: {},\n activeDocumentId: null,\n};\n\nexport const reducer: Reducer<HistoryState, HistoryAction> = (state = initialState, action) => {\n switch (action.type) {\n case INIT_HISTORY_STATE: {\n const { documentId } = action.payload;\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: { ...initialDocumentState },\n },\n };\n }\n\n case CLEANUP_HISTORY_STATE: {\n const { documentId } = action.payload;\n const { [documentId]: removed, ...remainingDocs } = state.documents;\n\n return {\n ...state,\n documents: remainingDocs,\n activeDocumentId: state.activeDocumentId === documentId ? null : state.activeDocumentId,\n };\n }\n\n case SET_HISTORY_DOCUMENT_STATE: {\n const { documentId, state: docState } = action.payload;\n if (!state.documents[documentId]) {\n return state;\n }\n\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: docState,\n },\n };\n }\n\n case SET_ACTIVE_HISTORY_DOCUMENT: {\n return {\n ...state,\n activeDocumentId: action.payload,\n };\n }\n\n default:\n return state;\n }\n};\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, HISTORY_PLUGIN_ID } from './manifest';\nimport { HistoryPluginConfig, HistoryState } from './types';\nimport { HistoryPlugin } from './history-plugin';\nimport { initialState, reducer } from './reducer';\nimport { HistoryAction } from './actions';\n\nexport const HistoryPluginPackage: PluginPackage<\n HistoryPlugin,\n HistoryPluginConfig,\n HistoryState,\n HistoryAction\n> = {\n manifest,\n create: (registry) => new HistoryPlugin(HISTORY_PLUGIN_ID, registry),\n reducer,\n initialState,\n};\n\nexport * from './history-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/lib/manifest.ts","../src/lib/actions.ts","../src/lib/history-plugin.ts","../src/lib/reducer.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { HistoryPluginConfig } from './types';\n\nexport const HISTORY_PLUGIN_ID = 'history';\n\nexport const manifest: PluginManifest<HistoryPluginConfig> = {\n id: HISTORY_PLUGIN_ID,\n name: 'History Plugin',\n version: '1.0.0',\n provides: ['history'],\n requires: [],\n optional: [],\n defaultConfig: {},\n};\n","import { Action } from '@embedpdf/core';\nimport { HistoryDocumentState } from './types';\n\n// Document lifecycle actions\nexport const INIT_HISTORY_STATE = 'HISTORY/INIT_STATE';\nexport const CLEANUP_HISTORY_STATE = 'HISTORY/CLEANUP_STATE';\n\n// History state updates\nexport const SET_HISTORY_DOCUMENT_STATE = 'HISTORY/SET_DOCUMENT_STATE';\nexport const SET_ACTIVE_HISTORY_DOCUMENT = 'HISTORY/SET_ACTIVE_DOCUMENT';\n\n// Document lifecycle action interfaces\nexport interface InitHistoryStateAction extends Action {\n type: typeof INIT_HISTORY_STATE;\n payload: {\n documentId: string;\n };\n}\n\nexport interface CleanupHistoryStateAction extends Action {\n type: typeof CLEANUP_HISTORY_STATE;\n payload: {\n documentId: string;\n };\n}\n\n// State update action interfaces\nexport interface SetHistoryDocumentStateAction extends Action {\n type: typeof SET_HISTORY_DOCUMENT_STATE;\n payload: {\n documentId: string;\n state: HistoryDocumentState;\n };\n}\n\nexport interface SetActiveHistoryDocumentAction extends Action {\n type: typeof SET_ACTIVE_HISTORY_DOCUMENT;\n payload: string | null; // documentId\n}\n\nexport type HistoryAction =\n | InitHistoryStateAction\n | CleanupHistoryStateAction\n | SetHistoryDocumentStateAction\n | SetActiveHistoryDocumentAction;\n\n// Action creators\nexport const initHistoryState = (documentId: string): InitHistoryStateAction => ({\n type: INIT_HISTORY_STATE,\n payload: { documentId },\n});\n\nexport const cleanupHistoryState = (documentId: string): CleanupHistoryStateAction => ({\n type: CLEANUP_HISTORY_STATE,\n payload: { documentId },\n});\n\nexport const setHistoryDocumentState = (\n documentId: string,\n state: HistoryDocumentState,\n): SetHistoryDocumentStateAction => ({\n type: SET_HISTORY_DOCUMENT_STATE,\n payload: { documentId, state },\n});\n\nexport const setActiveHistoryDocument = (\n documentId: string | null,\n): SetActiveHistoryDocumentAction => ({\n type: SET_ACTIVE_HISTORY_DOCUMENT,\n payload: documentId,\n});\n","import { BasePlugin, createEmitter, Listener, PluginRegistry } from '@embedpdf/core';\nimport {\n Command,\n HistoryCapability,\n HistoryChangeEvent,\n HistoryDocumentState,\n HistoryEntry,\n HistoryPluginConfig,\n HistoryScope,\n HistoryState,\n} from './types';\nimport {\n HistoryAction,\n initHistoryState,\n cleanupHistoryState,\n setHistoryDocumentState,\n} from './actions';\n\ninterface DocumentHistoryData {\n topicHistories: Map<string, { commands: Command[]; currentIndex: number }>;\n globalTimeline: HistoryEntry[];\n globalIndex: number;\n}\n\nexport class HistoryPlugin extends BasePlugin<\n HistoryPluginConfig,\n HistoryCapability,\n HistoryState,\n HistoryAction\n> {\n static readonly id = 'history' as const;\n\n // Per-document history data (persisted with state)\n private readonly documentHistories = new Map<string, DocumentHistoryData>();\n\n // Event emitter for history changes\n private readonly historyChange$ = createEmitter<HistoryChangeEvent>();\n\n constructor(id: string, registry: PluginRegistry) {\n super(id, registry);\n }\n\n async initialize(_: HistoryPluginConfig): Promise<void> {\n this.logger.info('HistoryPlugin', 'Initialize', 'History plugin initialized');\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Lifecycle (from BasePlugin)\n // ─────────────────────────────────────────────────────────\n\n protected override onDocumentLoadingStarted(documentId: string): void {\n // Initialize history state for this document\n this.dispatch(initHistoryState(documentId));\n\n // Create document history data\n this.documentHistories.set(documentId, {\n topicHistories: new Map(),\n globalTimeline: [],\n globalIndex: -1,\n });\n\n this.logger.debug(\n 'HistoryPlugin',\n 'DocumentOpened',\n `Initialized history state for document: ${documentId}`,\n );\n }\n\n protected override onDocumentClosed(documentId: string): void {\n // Cleanup history state\n this.dispatch(cleanupHistoryState(documentId));\n\n // Cleanup document history data\n this.documentHistories.delete(documentId);\n\n this.logger.debug(\n 'HistoryPlugin',\n 'DocumentClosed',\n `Cleaned up history state for document: ${documentId}`,\n );\n }\n\n // ─────────────────────────────────────────────────────────\n // Helper Methods\n // ─────────────────────────────────────────────────────────\n\n private getDocumentHistoryData(documentId?: string): DocumentHistoryData {\n const id = documentId ?? this.getActiveDocumentId();\n const data = this.documentHistories.get(id);\n if (!data) {\n throw new Error(`History data not found for document: ${id}`);\n }\n return data;\n }\n\n private getDocumentHistoryState(documentId: string): HistoryDocumentState {\n const data = this.documentHistories.get(documentId);\n if (!data) {\n throw new Error(`History data not found for document: ${documentId}`);\n }\n\n const topics: HistoryDocumentState['topics'] = {};\n Array.from(data.topicHistories.entries()).forEach(([topic, history]) => {\n topics[topic] = {\n canUndo: history.currentIndex > -1,\n canRedo: history.currentIndex < history.commands.length - 1,\n };\n });\n\n return {\n global: {\n canUndo: data.globalIndex > -1,\n canRedo: data.globalIndex < data.globalTimeline.length - 1,\n },\n topics,\n };\n }\n\n private emitHistoryChange(documentId: string, topic: string | undefined) {\n // Update the state\n const state = this.getDocumentHistoryState(documentId);\n this.dispatch(setHistoryDocumentState(documentId, state));\n\n // Emit the event\n this.historyChange$.emit({\n documentId,\n topic,\n state,\n });\n }\n\n // ─────────────────────────────────────────────────────────\n // History Operations (per document)\n // ─────────────────────────────────────────────────────────\n\n private register(command: Command, topic: string, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n\n // 1. Manage Topic History\n if (!data.topicHistories.has(topic)) {\n data.topicHistories.set(topic, { commands: [], currentIndex: -1 });\n }\n const topicHistory = data.topicHistories.get(topic)!;\n topicHistory.commands.splice(topicHistory.currentIndex + 1);\n topicHistory.commands.push(command);\n topicHistory.currentIndex++;\n\n // 2. Manage Global History\n const historyEntry: HistoryEntry = { command, topic };\n data.globalTimeline.splice(data.globalIndex + 1);\n data.globalTimeline.push(historyEntry);\n data.globalIndex++;\n\n // 3. Execute and notify with the specific topic\n command.execute();\n this.emitHistoryChange(documentId, topic);\n }\n\n private undo(topic: string | undefined, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n let affectedTopic: string | undefined;\n\n if (topic) {\n // Scoped Undo\n const topicHistory = data.topicHistories.get(topic);\n if (topicHistory && topicHistory.currentIndex > -1) {\n topicHistory.commands[topicHistory.currentIndex].undo();\n topicHistory.currentIndex--;\n affectedTopic = topic;\n }\n } else {\n // Global Undo\n if (data.globalIndex > -1) {\n const entry = data.globalTimeline[data.globalIndex];\n entry.command.undo();\n data.topicHistories.get(entry.topic)!.currentIndex--;\n data.globalIndex--;\n affectedTopic = entry.topic;\n }\n }\n\n if (affectedTopic) {\n this.emitHistoryChange(documentId, affectedTopic);\n }\n }\n\n private redo(topic: string | undefined, documentId: string): void {\n const data = this.getDocumentHistoryData(documentId);\n let affectedTopic: string | undefined;\n\n if (topic) {\n // Scoped Redo\n const topicHistory = data.topicHistories.get(topic);\n if (topicHistory && topicHistory.currentIndex < topicHistory.commands.length - 1) {\n topicHistory.currentIndex++;\n topicHistory.commands[topicHistory.currentIndex].execute();\n affectedTopic = topic;\n }\n } else {\n // Global Redo\n if (data.globalIndex < data.globalTimeline.length - 1) {\n data.globalIndex++;\n const entry = data.globalTimeline[data.globalIndex];\n entry.command.execute();\n data.topicHistories.get(entry.topic)!.currentIndex++;\n affectedTopic = entry.topic;\n }\n }\n\n if (affectedTopic) {\n this.emitHistoryChange(documentId, affectedTopic);\n }\n }\n\n private canUndo(topic: string | undefined, documentId: string): boolean {\n const data = this.getDocumentHistoryData(documentId);\n\n if (topic) {\n const history = data.topicHistories.get(topic);\n return !!history && history.currentIndex > -1;\n }\n return data.globalIndex > -1;\n }\n\n private canRedo(topic: string | undefined, documentId: string): boolean {\n const data = this.getDocumentHistoryData(documentId);\n\n if (topic) {\n const history = data.topicHistories.get(topic);\n return !!history && history.currentIndex < history.commands.length - 1;\n }\n return data.globalIndex < data.globalTimeline.length - 1;\n }\n\n /**\n * Purges history entries that match the given predicate based on command metadata.\n * Used to remove commands that are no longer valid (e.g., after a permanent redaction commit).\n *\n * @param predicate A function that returns true for commands that should be purged\n * @param topic If provided, only purges entries for that specific topic\n * @param documentId The document to purge history for\n * @returns The number of entries that were purged\n */\n private purgeByMetadata<T>(\n predicate: (metadata: T | undefined) => boolean,\n topic: string | undefined,\n documentId: string,\n ): number {\n const data = this.getDocumentHistoryData(documentId);\n let purgedCount = 0;\n\n // Helper to check if a command should be purged\n const shouldPurge = (command: Command): boolean => {\n return predicate(command.metadata as T | undefined);\n };\n\n // 1. Purge from topic histories\n const topicsToProcess = topic ? [topic] : Array.from(data.topicHistories.keys());\n\n for (const topicName of topicsToProcess) {\n const topicHistory = data.topicHistories.get(topicName);\n if (!topicHistory) continue;\n\n const newCommands: Command[] = [];\n let indexAdjustment = 0;\n\n for (let i = 0; i < topicHistory.commands.length; i++) {\n const command = topicHistory.commands[i];\n if (shouldPurge(command)) {\n // If this entry is at or before currentIndex, we need to adjust\n if (i <= topicHistory.currentIndex) {\n indexAdjustment++;\n }\n purgedCount++;\n } else {\n newCommands.push(command);\n }\n }\n\n topicHistory.commands = newCommands;\n topicHistory.currentIndex = Math.max(-1, topicHistory.currentIndex - indexAdjustment);\n }\n\n // 2. Purge from global timeline\n const newTimeline: HistoryEntry[] = [];\n let globalIndexAdjustment = 0;\n\n for (let i = 0; i < data.globalTimeline.length; i++) {\n const entry = data.globalTimeline[i];\n const matchesTopic = !topic || entry.topic === topic;\n\n if (matchesTopic && shouldPurge(entry.command)) {\n // If this entry is at or before globalIndex, we need to adjust\n if (i <= data.globalIndex) {\n globalIndexAdjustment++;\n }\n } else {\n newTimeline.push(entry);\n }\n }\n\n data.globalTimeline = newTimeline;\n data.globalIndex = Math.max(-1, data.globalIndex - globalIndexAdjustment);\n\n // 3. Emit history change if anything was purged\n if (purgedCount > 0) {\n this.emitHistoryChange(documentId, topic);\n this.logger.debug(\n 'HistoryPlugin',\n 'PurgeByMetadata',\n `Purged ${purgedCount} history entries for document: ${documentId}${topic ? `, topic: ${topic}` : ''}`,\n );\n }\n\n return purgedCount;\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Scoping\n // ─────────────────────────────────────────────────────────\n\n private createHistoryScope(documentId: string): HistoryScope {\n return {\n register: (command: Command, topic: string) => this.register(command, topic, documentId),\n undo: (topic?: string) => this.undo(topic, documentId),\n redo: (topic?: string) => this.redo(topic, documentId),\n canUndo: (topic?: string) => this.canUndo(topic, documentId),\n canRedo: (topic?: string) => this.canRedo(topic, documentId),\n getHistoryState: () => this.getDocumentHistoryState(documentId),\n onHistoryChange: (listener: Listener<string | undefined>) =>\n this.historyChange$.on((event) => {\n if (event.documentId === documentId) {\n listener(event.topic);\n }\n }),\n purgeByMetadata: <T>(predicate: (metadata: T | undefined) => boolean, topic?: string) =>\n this.purgeByMetadata(predicate, topic, documentId),\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Capability\n // ─────────────────────────────────────────────────────────\n\n protected buildCapability(): HistoryCapability {\n return {\n // Active document operations\n register: (command: Command, topic: string) => {\n const documentId = this.getActiveDocumentId();\n this.register(command, topic, documentId);\n },\n\n undo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n this.undo(topic, documentId);\n },\n\n redo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n this.redo(topic, documentId);\n },\n\n canUndo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.canUndo(topic, documentId);\n },\n\n canRedo: (topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.canRedo(topic, documentId);\n },\n\n getHistoryState: () => {\n const documentId = this.getActiveDocumentId();\n return this.getDocumentHistoryState(documentId);\n },\n\n // Document-scoped operations\n forDocument: (documentId: string) => this.createHistoryScope(documentId),\n\n // Events\n onHistoryChange: this.historyChange$.on,\n\n // Purge operations\n purgeByMetadata: <T>(predicate: (metadata: T | undefined) => boolean, topic?: string) => {\n const documentId = this.getActiveDocumentId();\n return this.purgeByMetadata(predicate, topic, documentId);\n },\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Lifecycle\n // ─────────────────────────────────────────────────────────\n\n async destroy(): Promise<void> {\n // Clear all emitters\n this.historyChange$.clear();\n\n // Clear document histories\n this.documentHistories.clear();\n\n super.destroy();\n }\n}\n","import { Reducer } from '@embedpdf/core';\nimport {\n HistoryAction,\n INIT_HISTORY_STATE,\n CLEANUP_HISTORY_STATE,\n SET_HISTORY_DOCUMENT_STATE,\n SET_ACTIVE_HISTORY_DOCUMENT,\n} from './actions';\nimport { HistoryState, HistoryDocumentState } from './types';\n\nconst initialDocumentState: HistoryDocumentState = {\n global: {\n canUndo: false,\n canRedo: false,\n },\n topics: {},\n};\n\nexport const initialState: HistoryState = {\n documents: {},\n activeDocumentId: null,\n};\n\nexport const reducer: Reducer<HistoryState, HistoryAction> = (state = initialState, action) => {\n switch (action.type) {\n case INIT_HISTORY_STATE: {\n const { documentId } = action.payload;\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: { ...initialDocumentState },\n },\n };\n }\n\n case CLEANUP_HISTORY_STATE: {\n const { documentId } = action.payload;\n const { [documentId]: removed, ...remainingDocs } = state.documents;\n\n return {\n ...state,\n documents: remainingDocs,\n activeDocumentId: state.activeDocumentId === documentId ? null : state.activeDocumentId,\n };\n }\n\n case SET_HISTORY_DOCUMENT_STATE: {\n const { documentId, state: docState } = action.payload;\n if (!state.documents[documentId]) {\n return state;\n }\n\n return {\n ...state,\n documents: {\n ...state.documents,\n [documentId]: docState,\n },\n };\n }\n\n case SET_ACTIVE_HISTORY_DOCUMENT: {\n return {\n ...state,\n activeDocumentId: action.payload,\n };\n }\n\n default:\n return state;\n }\n};\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, HISTORY_PLUGIN_ID } from './manifest';\nimport { HistoryPluginConfig, HistoryState } from './types';\nimport { HistoryPlugin } from './history-plugin';\nimport { initialState, reducer } from './reducer';\nimport { HistoryAction } from './actions';\n\nexport const HistoryPluginPackage: PluginPackage<\n HistoryPlugin,\n HistoryPluginConfig,\n HistoryState,\n HistoryAction\n> = {\n manifest,\n create: (registry) => new HistoryPlugin(HISTORY_PLUGIN_ID, registry),\n reducer,\n initialState,\n};\n\nexport * from './history-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":[],"mappings":"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"}
@@ -17,6 +17,16 @@ export declare class HistoryPlugin extends BasePlugin<HistoryPluginConfig, Histo
17
17
  private redo;
18
18
  private canUndo;
19
19
  private canRedo;
20
+ /**
21
+ * Purges history entries that match the given predicate based on command metadata.
22
+ * Used to remove commands that are no longer valid (e.g., after a permanent redaction commit).
23
+ *
24
+ * @param predicate A function that returns true for commands that should be purged
25
+ * @param topic If provided, only purges entries for that specific topic
26
+ * @param documentId The document to purge history for
27
+ * @returns The number of entries that were purged
28
+ */
29
+ private purgeByMetadata;
20
30
  private createHistoryScope;
21
31
  protected buildCapability(): HistoryCapability;
22
32
  destroy(): Promise<void>;
@@ -3,12 +3,15 @@ export interface HistoryPluginConfig extends BasePluginConfig {
3
3
  }
4
4
  /**
5
5
  * The core Command interface that other plugins will implement.
6
+ * @template T - Optional metadata type for identifying/filtering commands
6
7
  */
7
- export interface Command {
8
+ export interface Command<T = unknown> {
8
9
  /** A function that applies the change. */
9
10
  execute(): void;
10
11
  /** A function that reverts the change. */
11
12
  undo(): void;
13
+ /** Optional metadata for identifying/filtering commands (e.g., for purging) */
14
+ metadata?: T;
12
15
  }
13
16
  /**
14
17
  * An entry in the global timeline, associating a command with its topic.
@@ -87,6 +90,14 @@ export interface HistoryScope {
87
90
  * @param topic The topic string that was affected by the action.
88
91
  */
89
92
  onHistoryChange: EventHook<string | undefined>;
93
+ /**
94
+ * Purges history entries that match the given predicate based on command metadata.
95
+ * Used to remove commands that are no longer valid (e.g., after a permanent redaction commit).
96
+ * @param predicate A function that returns true for commands that should be purged
97
+ * @param topic If provided, only purges entries for that specific topic
98
+ * @returns The number of entries that were purged
99
+ */
100
+ purgeByMetadata: <T>(predicate: (metadata: T | undefined) => boolean, topic?: string) => number;
90
101
  }
91
102
  export interface HistoryCapability {
92
103
  /**
@@ -129,4 +140,12 @@ export interface HistoryCapability {
129
140
  * An event hook that fires whenever a history action occurs.
130
141
  */
131
142
  onHistoryChange: EventHook<HistoryChangeEvent>;
143
+ /**
144
+ * Purges history entries that match the given predicate based on command metadata.
145
+ * Used to remove commands that are no longer valid (e.g., after a permanent redaction commit).
146
+ * @param predicate A function that returns true for commands that should be purged
147
+ * @param topic If provided, only purges entries for that specific topic
148
+ * @returns The number of entries that were purged
149
+ */
150
+ purgeByMetadata: <T>(predicate: (metadata: T | undefined) => boolean, topic?: string) => number;
132
151
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-history",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",
@@ -35,13 +35,13 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@embedpdf/models": "2.3.0"
38
+ "@embedpdf/models": "2.4.1"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/react": "^18.2.0",
42
42
  "typescript": "^5.0.0",
43
43
  "@embedpdf/build": "1.1.0",
44
- "@embedpdf/core": "2.3.0"
44
+ "@embedpdf/core": "2.4.1"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "react": ">=16.8.0",
@@ -49,7 +49,7 @@
49
49
  "preact": "^10.26.4",
50
50
  "vue": ">=3.2.0",
51
51
  "svelte": ">=5 <6",
52
- "@embedpdf/core": "2.3.0"
52
+ "@embedpdf/core": "2.4.1"
53
53
  },
54
54
  "files": [
55
55
  "dist",