@inshapardaz/likhari-react 0.1.7-dev.88 → 0.1.8

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.
@@ -1,198 +0,0 @@
1
- /**
2
- * Autosave-to-localStorage drafts (lexical-editor-spec.md §6.2). Each draft is
3
- * keyed by an id: the host's `documentId` when it gives one (§6.1), otherwise
4
- * a generated unique id, so editors without a `documentId` never overwrite each
5
- * other's drafts. Every function is a no-op (never throws) when `localStorage`
6
- * is unavailable (SSR, privacy mode) or a stored value is corrupt — autosave is
7
- * best-effort, never a hard dependency.
8
- */
9
- const KEY_PREFIX = 'likhari:draft:';
10
- const PREVIEW_LENGTH = 140;
11
- export function draftKey(id) {
12
- return `${KEY_PREFIX}${id}`;
13
- }
14
- /** A new unique id for a draft whose editor has no `documentId`. */
15
- export function createDraftId() {
16
- try {
17
- if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
18
- return crypto.randomUUID();
19
- }
20
- catch {
21
- // fall through to the non-crypto id
22
- }
23
- return `draft-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
24
- }
25
- /** Whether an id came from createDraftId() rather than a host's `documentId`. */
26
- export function isGeneratedDraftId(id) {
27
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id) || /^draft-[0-9a-z]+-[0-9a-z]+$/.test(id);
28
- }
29
- function getLocalStorage() {
30
- try {
31
- return typeof localStorage === 'undefined' ? null : localStorage;
32
- }
33
- catch {
34
- // Some browsers throw on accessing localStorage in certain privacy modes.
35
- return null;
36
- }
37
- }
38
- function collectText(node, parts, state) {
39
- if (node.type === 'text' && typeof node.text === 'string') {
40
- if (node.text.trim())
41
- state.hasContent = true;
42
- parts.push(node.text);
43
- }
44
- else if (node.type === 'linebreak') {
45
- parts.push(' ');
46
- }
47
- else if (node.type === 'image') {
48
- state.hasContent = true;
49
- if (node.altText)
50
- parts.push(`[${node.altText}]`);
51
- }
52
- for (const child of node.children ?? [])
53
- collectText(child, parts, state);
54
- // Block boundaries read as a space in a one-line preview.
55
- if (node.children && node.type !== 'root')
56
- parts.push(' ');
57
- }
58
- function summarize(json) {
59
- try {
60
- const root = JSON.parse(json).root;
61
- if (!root)
62
- return { preview: '', hasContent: false };
63
- const parts = [];
64
- const state = { hasContent: false };
65
- collectText(root, parts, state);
66
- const text = parts.join('').replace(/\s+/g, ' ').trim();
67
- const preview = text.length > PREVIEW_LENGTH ? `${text.slice(0, PREVIEW_LENGTH).trimEnd()}…` : text;
68
- return { preview, hasContent: state.hasContent };
69
- }
70
- catch {
71
- return { preview: '', hasContent: false };
72
- }
73
- }
74
- /** One-line text preview of a Lexical JSON document ('' when it has no text). */
75
- export function extractPreview(json) {
76
- return summarize(json).preview;
77
- }
78
- /** Whether a Lexical JSON document has any text or image (a blank one isn't worth a draft). */
79
- export function hasDraftContent(json) {
80
- return summarize(json).hasContent;
81
- }
82
- function parseDraft(raw) {
83
- if (!raw)
84
- return null;
85
- try {
86
- const parsed = JSON.parse(raw);
87
- if (typeof parsed.json !== 'string' || typeof parsed.savedAt !== 'number' || parsed.version !== 1)
88
- return null;
89
- return parsed;
90
- }
91
- catch {
92
- return null;
93
- }
94
- }
95
- export function readDraft(id) {
96
- const storage = getLocalStorage();
97
- if (!storage)
98
- return null;
99
- try {
100
- return parseDraft(storage.getItem(draftKey(id)));
101
- }
102
- catch {
103
- return null;
104
- }
105
- }
106
- /**
107
- * Best-effort write; returns whether the draft was actually stored (callers that
108
- * promise the user a saved draft must check). Skips (rather than throwing) when `json` exceeds
109
- * `maxBytes` — localStorage has a shared ~5-10MB per-origin ceiling (§6.2), so a
110
- * single large document (e.g. embedded base64 images) must not be allowed to
111
- * autosave past a caller-configured limit — or when the write itself fails
112
- * (quota exceeded, privacy mode).
113
- */
114
- export function writeDraft(id, json, maxBytes, sourceId) {
115
- const storage = getLocalStorage();
116
- if (!storage)
117
- return false;
118
- // .length (UTF-16 code units) over-estimates UTF-8 byte size for any
119
- // non-Latin text, which only makes this check more conservative — fine
120
- // for a soft cap that doesn't need to be exact.
121
- if (json.length > maxBytes)
122
- return false;
123
- const draft = { json, savedAt: Date.now(), version: 1, preview: extractPreview(json) };
124
- if (sourceId)
125
- draft.sourceId = sourceId;
126
- try {
127
- storage.setItem(draftKey(id), JSON.stringify(draft));
128
- return true;
129
- }
130
- catch {
131
- // Quota exceeded or similar — autosave is best-effort.
132
- return false;
133
- }
134
- }
135
- export function clearDraft(id) {
136
- const storage = getLocalStorage();
137
- if (!storage)
138
- return;
139
- try {
140
- storage.removeItem(draftKey(id));
141
- }
142
- catch {
143
- // ignore
144
- }
145
- }
146
- /** Every stored draft, newest first. */
147
- export function listDrafts() {
148
- const storage = getLocalStorage();
149
- if (!storage)
150
- return [];
151
- const entries = [];
152
- try {
153
- for (let i = 0; i < storage.length; i++) {
154
- const key = storage.key(i);
155
- if (!key || !key.startsWith(KEY_PREFIX))
156
- continue;
157
- const draft = parseDraft(storage.getItem(key));
158
- if (draft)
159
- entries.push({ id: key.slice(KEY_PREFIX.length), draft });
160
- }
161
- }
162
- catch {
163
- return entries;
164
- }
165
- return entries.sort((a, b) => b.draft.savedAt - a.draft.savedAt);
166
- }
167
- /**
168
- * Keeps a draft that is about to be overwritten or ignored: copies it to a new
169
- * generated id (remembering `sourceId`) so it stays in the drafts list.
170
- * Returns the new id, or null if nothing could be copied.
171
- */
172
- export function archiveDraft(id) {
173
- const storage = getLocalStorage();
174
- const draft = readDraft(id);
175
- if (!storage || !draft)
176
- return null;
177
- const archivedId = createDraftId();
178
- try {
179
- storage.setItem(draftKey(archivedId), JSON.stringify({ ...draft, sourceId: draft.sourceId ?? id }));
180
- return archivedId;
181
- }
182
- catch {
183
- return null;
184
- }
185
- }
186
- /** Deletes the oldest drafts beyond `max`, never touching `keepIds`. */
187
- export function pruneDrafts(max, keepIds = []) {
188
- if (max < 1)
189
- return;
190
- const removable = listDrafts().filter((entry) => !keepIds.includes(entry.id));
191
- const kept = listDrafts().length - removable.length;
192
- const excess = removable.length + kept - max;
193
- if (excess <= 0)
194
- return;
195
- // removable is newest first, so the oldest are at the end.
196
- for (const entry of removable.slice(-excess))
197
- clearDraft(entry.id);
198
- }
@@ -1,9 +0,0 @@
1
- import type { LexicalEditor } from 'lexical';
2
- import type { Locale } from '../i18n/index.js';
3
- /** Replaces the editor's content with a stored draft's Lexical JSON. Returns
4
- * false (leaving the editor untouched) if the draft is corrupt or from an
5
- * incompatible node schema, rather than throwing. */
6
- export declare function applyDraftToEditor(editor: LexicalEditor, json: string): boolean;
7
- /** A draft's save time as a short localized date and time. */
8
- export declare function formatDraftTime(savedAt: number, locale: Locale): string;
9
- //# sourceMappingURL=draftUi.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"draftUi.d.ts","sourceRoot":"","sources":["../../src/persistence/draftUi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC;;qDAEqD;AACrD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAO/E;AAID,8DAA8D;AAC9D,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOvE"}
@@ -1,23 +0,0 @@
1
- /** Replaces the editor's content with a stored draft's Lexical JSON. Returns
2
- * false (leaving the editor untouched) if the draft is corrupt or from an
3
- * incompatible node schema, rather than throwing. */
4
- export function applyDraftToEditor(editor, json) {
5
- try {
6
- editor.setEditorState(editor.parseEditorState(json));
7
- return true;
8
- }
9
- catch {
10
- return false;
11
- }
12
- }
13
- const INTL_LOCALES = { en: 'en', ur: 'ur', 'pa-shahmukhi': 'pa-Arab' };
14
- /** A draft's save time as a short localized date and time. */
15
- export function formatDraftTime(savedAt, locale) {
16
- const date = new Date(savedAt);
17
- try {
18
- return new Intl.DateTimeFormat(INTL_LOCALES[locale], { dateStyle: 'medium', timeStyle: 'short' }).format(date);
19
- }
20
- catch {
21
- return date.toLocaleString();
22
- }
23
- }