mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
feat(renderer): editor store with buffers, dirty state, cursor (immer)
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { immer } from 'zustand/middleware/immer';
|
||||||
|
import { enableMapSet } from 'immer';
|
||||||
|
|
||||||
|
enableMapSet();
|
||||||
|
|
||||||
|
export interface Buffer {
|
||||||
|
id: string;
|
||||||
|
path: string;
|
||||||
|
content: string;
|
||||||
|
dirty: boolean;
|
||||||
|
cursor?: { line: number; column: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditorState {
|
||||||
|
buffers: Map<string, Buffer>;
|
||||||
|
activeId: string | null;
|
||||||
|
openBuffer: (id: string, path: string, content: string) => void;
|
||||||
|
updateContent: (id: string, content: string) => void;
|
||||||
|
markSaved: (id: string) => void;
|
||||||
|
setCursor: (id: string, line: number, column: number) => void;
|
||||||
|
closeBuffer: (id: string) => void;
|
||||||
|
setActive: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useEditorStore = create<EditorState>()(
|
||||||
|
immer((set) => ({
|
||||||
|
buffers: new Map(),
|
||||||
|
activeId: null,
|
||||||
|
openBuffer: (id, path, content) =>
|
||||||
|
set((s) => {
|
||||||
|
s.buffers.set(id, { id, path, content, dirty: false });
|
||||||
|
s.activeId = id;
|
||||||
|
}),
|
||||||
|
updateContent: (id, content) =>
|
||||||
|
set((s) => {
|
||||||
|
const buf = s.buffers.get(id);
|
||||||
|
if (buf) {
|
||||||
|
buf.content = content;
|
||||||
|
buf.dirty = true;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
markSaved: (id) =>
|
||||||
|
set((s) => {
|
||||||
|
const buf = s.buffers.get(id);
|
||||||
|
if (buf) buf.dirty = false;
|
||||||
|
}),
|
||||||
|
setCursor: (id, line, column) =>
|
||||||
|
set((s) => {
|
||||||
|
const buf = s.buffers.get(id);
|
||||||
|
if (buf) buf.cursor = { line, column };
|
||||||
|
}),
|
||||||
|
closeBuffer: (id) =>
|
||||||
|
set((s) => {
|
||||||
|
s.buffers.delete(id);
|
||||||
|
if (s.activeId === id) s.activeId = null;
|
||||||
|
}),
|
||||||
|
setActive: (id) =>
|
||||||
|
set((s) => {
|
||||||
|
s.activeId = id;
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { useEditorStore } from '@/stores/editor-store';
|
||||||
|
|
||||||
|
describe('useEditorStore', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
useEditorStore.setState({ buffers: new Map(), activeId: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new buffer with the given content', () => {
|
||||||
|
useEditorStore.getState().openBuffer('file-1', '/foo.md', '# hello');
|
||||||
|
const buf = useEditorStore.getState().buffers.get('file-1');
|
||||||
|
expect(buf?.content).toBe('# hello');
|
||||||
|
expect(buf?.path).toBe('/foo.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates content and marks dirty', () => {
|
||||||
|
useEditorStore.getState().openBuffer('file-1', '/foo.md', '');
|
||||||
|
useEditorStore.getState().updateContent('file-1', 'new content');
|
||||||
|
const buf = useEditorStore.getState().buffers.get('file-1');
|
||||||
|
expect(buf?.content).toBe('new content');
|
||||||
|
expect(buf?.dirty).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks a buffer clean after save', () => {
|
||||||
|
useEditorStore.getState().openBuffer('file-1', '/foo.md', '');
|
||||||
|
useEditorStore.getState().updateContent('file-1', 'x');
|
||||||
|
useEditorStore.getState().markSaved('file-1');
|
||||||
|
expect(useEditorStore.getState().buffers.get('file-1')?.dirty).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes a buffer and removes it', () => {
|
||||||
|
useEditorStore.getState().openBuffer('file-1', '/foo.md', '');
|
||||||
|
useEditorStore.getState().closeBuffer('file-1');
|
||||||
|
expect(useEditorStore.getState().buffers.has('file-1')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user