@vishu1301/script-writing 0.4.3 → 0.4.5

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
1
+ import { useState, useRef, useMemo, useCallback, useEffect } from 'react';
2
2
  import { ArrowRightLeft, MessageCircle, Brackets, UserRound, Sparkles, Clapperboard, ArrowRight, User, ChevronRight, Upload, Save, FileDown, Cog } from 'lucide-react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import * as pdfjs from 'pdfjs-dist';
@@ -220,9 +220,6 @@ function PdfImporter({ onScriptImported, children }) {
220
220
  }
221
221
  function ScreenplayEditorView({
222
222
  blocks,
223
- pages,
224
- isPageSplitEnabled,
225
- togglePageSplit,
226
223
  refs,
227
224
  focusedBlockId,
228
225
  showSuggestions,
@@ -285,131 +282,56 @@ function ScreenplayEditorView({
285
282
  type
286
283
  );
287
284
  }) }),
288
- /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-12 w-full items-center pb-24", children: pages.map((pageBlocks, pageIndex) => /* @__PURE__ */ jsxs(
285
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-12 w-full items-center pb-24", children: /* @__PURE__ */ jsx(
289
286
  "div",
290
287
  {
291
288
  className: "relative bg-[#fdfdfc] shadow-2xl shadow-zinc-300/60 ring-1 ring-zinc-200/50 rounded-sm md:rounded-md pl-[1.5in] py-[1in] pr-[1in] flex flex-col w-[210mm] min-h-[297mm] shrink-0",
292
289
  style: {
293
290
  fontFamily: "var(--font-courier-prime, 'Courier New', Courier, monospace)"
294
291
  },
295
- children: [
296
- pageBlocks.map((block) => {
297
- var _a, _b;
298
- return /* @__PURE__ */ jsx(
299
- "div",
300
- {
301
- "data-block-id": block.id,
302
- className: `relative rounded-sm transition-all duration-200 outline-none ${focusedBlockId === block.id ? "bg-zinc-100/50" : "bg-transparent"}`,
303
- children: block.type === "SCENE_HEADING" ? /* @__PURE__ */ jsxs(Fragment, { children: [
304
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-1 bg-transparent", children: [
305
- /* @__PURE__ */ jsx(
306
- "input",
307
- {
308
- className: "absolute -left-16 top-2 w-12 text-right text-zinc-400 font-semibold select-none bg-transparent outline-none focus:ring-1 focus:ring-blue-400 rounded-sm",
309
- spellCheck: false,
310
- value: block.sceneNumber || "",
311
- onChange: (e) => handleSceneNumberChange(
312
- block.id,
313
- e.target.value.toUpperCase()
314
- ),
315
- onFocus: () => handleFocus(block.id),
316
- onBlur: () => handleBlur(block.id),
317
- onKeyDown: (e) => {
318
- if (e.key === "Enter" || e.key === "Backspace") {
319
- e.stopPropagation();
320
- }
321
- },
322
- "aria-label": "Scene Number"
323
- }
324
- ),
325
- /* @__PURE__ */ jsxs(
326
- "select",
327
- {
328
- className: "rounded-md text-zinc-800 font-bold px-1.5 py-1 appearance-none bg-transparent hover:bg-zinc-200/50 outline-none cursor-pointer w-fit transition-colors",
329
- "aria-label": "Scene Type",
330
- value: (_a = block.sceneType) != null ? _a : "INT.",
331
- onChange: (e) => handleSceneTypeChange(block.id, e.target.value),
332
- children: [
333
- /* @__PURE__ */ jsx("option", { children: "INT." }),
334
- /* @__PURE__ */ jsx("option", { children: "EXT." }),
335
- /* @__PURE__ */ jsx("option", { children: "INT/EXT." })
336
- ]
337
- }
338
- ),
339
- /* @__PURE__ */ jsx(
340
- "div",
341
- {
342
- ref: (el) => {
343
- if (!el) return;
344
- refs.current[block.id] = el;
345
- },
346
- contentEditable: true,
347
- suppressContentEditableWarning: true,
348
- "aria-label": `Scene Heading: ${block.text}`,
349
- "aria-haspopup": "listbox",
350
- "aria-expanded": focusedBlockId === block.id && showSuggestions && locations.length > 0,
351
- spellCheck: false,
352
- className: "min-w-[3rem] py-1 outline-none text-base font-bold uppercase tracking-widest break-all bg-transparent",
353
- onInput: (e) => handleBlockTextChange(
354
- block.id,
355
- e.target.innerText
356
- ),
357
- onKeyDown: (e) => handleKeyDown(e, block.id, block.text),
358
- onFocus: () => handleFocus(block.id),
359
- onBlur: () => handleBlur(block.id)
360
- }
361
- ),
362
- /* @__PURE__ */ jsx("span", { className: "text-zinc-400/80 font-bold", children: "-" }),
363
- /* @__PURE__ */ jsx(
364
- "select",
365
- {
366
- className: "rounded-md text-zinc-800 font-bold px-1.5 py-1 appearance-none bg-transparent hover:bg-zinc-200/50 outline-none cursor-pointer transition-colors",
367
- "aria-label": "Time of Day",
368
- value: (_b = block.timeOfDay) != null ? _b : "DAY",
369
- onChange: (e) => handleTimeOfDayChange(block.id, e.target.value),
370
- children: timeOfDayOptions.map((t) => /* @__PURE__ */ jsx("option", { children: t }, t))
371
- }
372
- )
373
- ] }),
374
- focusedBlockId === block.id && showSuggestions && locations.length > 0 && /* @__PURE__ */ jsx(
375
- "div",
292
+ children: blocks.map((block) => {
293
+ var _a, _b;
294
+ return /* @__PURE__ */ jsx(
295
+ "div",
296
+ {
297
+ "data-block-id": block.id,
298
+ className: `relative rounded-sm transition-all duration-200 outline-none ${focusedBlockId === block.id ? "bg-zinc-100/50" : "bg-transparent"}`,
299
+ children: block.type === "SCENE_HEADING" ? /* @__PURE__ */ jsxs(Fragment, { children: [
300
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-1 bg-transparent", children: [
301
+ /* @__PURE__ */ jsx(
302
+ "input",
376
303
  {
377
- role: "listbox",
378
- id: `suggestions-${block.id}`,
379
- className: "absolute top-[calc(100%+6px)] left-0 min-w-[240px] z-50 bg-white border border-slate-200/80 shadow-xl shadow-slate-200/40 rounded-xl py-1 overflow-hidden animate-in fade-in zoom-in-95 duration-150",
380
- children: /* @__PURE__ */ jsx("div", { className: "max-h-60 overflow-y-auto custom-scrollbar", children: locations.filter(
381
- (loc) => loc.startsWith(block.text.toUpperCase()) && loc !== block.text.toUpperCase()
382
- ).map((loc) => /* @__PURE__ */ jsxs(
383
- "div",
384
- {
385
- role: "option",
386
- className: "group flex items-center justify-between px-4 py-2.5 cursor-pointer transition-all duration-150 hover:bg-slate-50 active:bg-slate-100",
387
- onMouseDown: (e) => {
388
- e.preventDefault();
389
- const element = refs.current[block.id];
390
- if (element) {
391
- element.innerText = loc;
392
- handleBlockTextChange(block.id, loc);
393
- element.focus();
394
- const range = document.createRange();
395
- const sel = window.getSelection();
396
- range.selectNodeContents(element);
397
- range.collapse(false);
398
- sel == null ? void 0 : sel.removeAllRanges();
399
- sel == null ? void 0 : sel.addRange(range);
400
- }
401
- handleBlur(block.id);
402
- },
403
- children: [
404
- /* @__PURE__ */ jsx("span", { className: "text-[12px] font-semibold tracking-wide text-slate-600 uppercase line-clamp-1", children: loc }),
405
- /* @__PURE__ */ jsx(ArrowRight, { className: "w-3.5 h-3.5 text-slate-300 opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200" })
406
- ]
407
- },
408
- loc
409
- )) })
304
+ className: "absolute -left-16 top-2 w-12 text-right text-zinc-400 font-semibold select-none bg-transparent outline-none focus:ring-1 focus:ring-blue-400 rounded-sm",
305
+ spellCheck: false,
306
+ value: block.sceneNumber || "",
307
+ onChange: (e) => handleSceneNumberChange(
308
+ block.id,
309
+ e.target.value.toUpperCase()
310
+ ),
311
+ onFocus: () => handleFocus(block.id),
312
+ onBlur: () => handleBlur(block.id),
313
+ onKeyDown: (e) => {
314
+ if (e.key === "Enter" || e.key === "Backspace") {
315
+ e.stopPropagation();
316
+ }
317
+ },
318
+ "aria-label": "Scene Number"
410
319
  }
411
- )
412
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
320
+ ),
321
+ /* @__PURE__ */ jsxs(
322
+ "select",
323
+ {
324
+ className: "rounded-md text-zinc-800 font-bold px-1.5 py-1 appearance-none bg-transparent hover:bg-zinc-200/50 outline-none cursor-pointer w-fit transition-colors",
325
+ "aria-label": "Scene Type",
326
+ value: (_a = block.sceneType) != null ? _a : "INT.",
327
+ onChange: (e) => handleSceneTypeChange(block.id, e.target.value),
328
+ children: [
329
+ /* @__PURE__ */ jsx("option", { children: "INT." }),
330
+ /* @__PURE__ */ jsx("option", { children: "EXT." }),
331
+ /* @__PURE__ */ jsx("option", { children: "INT/EXT." })
332
+ ]
333
+ }
334
+ ),
413
335
  /* @__PURE__ */ jsx(
414
336
  "div",
415
337
  {
@@ -419,100 +341,168 @@ function ScreenplayEditorView({
419
341
  },
420
342
  contentEditable: true,
421
343
  suppressContentEditableWarning: true,
422
- "aria-label": `${blockStyles[block.type].label} text`,
423
- "aria-multiline": block.type === "ACTION" || block.type === "DIALOGUE",
344
+ "aria-label": `Scene Heading: ${block.text}`,
345
+ "aria-haspopup": "listbox",
346
+ "aria-expanded": focusedBlockId === block.id && showSuggestions && locations.length > 0,
424
347
  spellCheck: false,
425
- className: `block outline-none w-full min-h-[2.5rem] px-4 py-2 break-words ${blockStyles[block.type].className}`,
348
+ className: "min-w-[3rem] py-1 outline-none text-base font-bold uppercase tracking-widest break-all bg-transparent",
426
349
  onInput: (e) => handleBlockTextChange(
427
350
  block.id,
428
351
  e.target.innerText
429
352
  ),
430
353
  onKeyDown: (e) => handleKeyDown(e, block.id, block.text),
431
354
  onFocus: () => handleFocus(block.id),
432
- onBlur: () => handleBlur(block.id),
433
- style: blockStyles[block.type].inputStyle
434
- }
435
- ),
436
- focusedBlockId === block.id && block.type === "CHARACTER" && showSuggestions && characters.length > 0 && /* @__PURE__ */ jsx(
437
- "div",
438
- {
439
- role: "listbox",
440
- id: `suggestions-${block.id}`,
441
- className: "absolute top-[calc(100%+8px)] left-1/2 -translate-x-1/2 w-72 z-50 bg-white border border-slate-200 shadow-2xl shadow-slate-200/60 rounded-xl py-2 overflow-hidden animate-in fade-in zoom-in-95 duration-200",
442
- children: /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto custom-scrollbar", children: characters.filter(
443
- (char) => char.startsWith(block.text.toUpperCase()) && char !== block.text.toUpperCase()
444
- ).map((char) => /* @__PURE__ */ jsxs(
445
- "div",
446
- {
447
- role: "option",
448
- className: "group flex items-center px-4 py-2.5 cursor-pointer transition-colors duration-150 hover:bg-slate-50 active:bg-slate-100",
449
- onMouseDown: (e) => {
450
- e.preventDefault();
451
- const element = refs.current[block.id];
452
- if (element) {
453
- element.innerText = char;
454
- handleBlockTextChange(block.id, char);
455
- element.focus();
456
- const range = document.createRange();
457
- const sel = window.getSelection();
458
- range.selectNodeContents(element);
459
- range.collapse(false);
460
- sel == null ? void 0 : sel.removeAllRanges();
461
- sel == null ? void 0 : sel.addRange(range);
462
- }
463
- handleBlur(block.id);
464
- },
465
- children: [
466
- /* @__PURE__ */ jsx(User, { className: "w-3.5 h-3.5 text-slate-300 group-hover:text-sky-500 transition-colors mr-3" }),
467
- /* @__PURE__ */ jsx("span", { className: "flex-1 text-[11px] font-bold tracking-[0.1em] text-slate-600 uppercase text-left", children: char }),
468
- /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3 text-slate-200 opacity-0 group-hover:opacity-100 transition-all -translate-x-1 group-hover:translate-x-0" })
469
- ]
470
- },
471
- char
472
- )) })
355
+ onBlur: () => handleBlur(block.id)
473
356
  }
474
357
  ),
475
- focusedBlockId === block.id && block.type === "CHARACTER" && showExtensionSuggestions && characterExtensions && /* @__PURE__ */ jsx(
476
- "div",
358
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-400/80 font-bold", children: "-" }),
359
+ /* @__PURE__ */ jsx(
360
+ "select",
477
361
  {
478
- role: "listbox",
479
- id: `extension-suggestions-${block.id}`,
480
- className: "absolute top-[calc(100%+8px)] left-1/2 -translate-x-1/2 w-72 z-50 bg-white border border-slate-200 shadow-2xl shadow-slate-200/60 rounded-xl py-2 overflow-hidden animate-in fade-in zoom-in-95 duration-200",
481
- children: /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto custom-scrollbar", children: characterExtensions.filter((ext) => {
482
- const openParenIndex = block.text.lastIndexOf("(");
483
- const query = openParenIndex > -1 ? block.text.substring(openParenIndex + 1).toUpperCase() : "";
484
- return ext.toUpperCase().includes(query);
485
- }).map((ext) => /* @__PURE__ */ jsxs(
486
- "div",
487
- {
488
- role: "option",
489
- className: "group flex items-center px-4 py-2.5 cursor-pointer transition-colors duration-150 hover:bg-slate-50 active:bg-slate-100",
490
- onMouseDown: (e) => {
491
- e.preventDefault();
492
- handleSelectCharacterExtension(ext);
493
- },
494
- children: [
495
- /* @__PURE__ */ jsx("span", { className: "flex-1 text-[11px] font-bold tracking-[0.1em] text-slate-600 uppercase text-left", children: ext }),
496
- /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3 text-slate-200 opacity-0 group-hover:opacity-100 transition-all -translate-x-1 group-hover:translate-x-0" })
497
- ]
498
- },
499
- ext
500
- )) })
362
+ className: "rounded-md text-zinc-800 font-bold px-1.5 py-1 appearance-none bg-transparent hover:bg-zinc-200/50 outline-none cursor-pointer transition-colors",
363
+ "aria-label": "Time of Day",
364
+ value: (_b = block.timeOfDay) != null ? _b : "DAY",
365
+ onChange: (e) => handleTimeOfDayChange(block.id, e.target.value),
366
+ children: timeOfDayOptions.map((t) => /* @__PURE__ */ jsx("option", { children: t }, t))
501
367
  }
502
368
  )
503
- ] })
504
- },
505
- block.id + "-" + block.type
506
- );
507
- }),
508
- isPageSplitEnabled && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-10 right-16 md:right-20 text-zinc-400 font-semibold text-sm select-none pointer-events-none", children: [
509
- pageIndex + 1,
510
- "."
511
- ] })
512
- ]
513
- },
514
- pageIndex
515
- )) }),
369
+ ] }),
370
+ focusedBlockId === block.id && showSuggestions && locations.length > 0 && /* @__PURE__ */ jsx(
371
+ "div",
372
+ {
373
+ role: "listbox",
374
+ id: `suggestions-${block.id}`,
375
+ className: "absolute top-[calc(100%+6px)] left-0 min-w-[240px] z-50 bg-white border border-slate-200/80 shadow-xl shadow-slate-200/40 rounded-xl py-1 overflow-hidden animate-in fade-in zoom-in-95 duration-150",
376
+ children: /* @__PURE__ */ jsx("div", { className: "max-h-60 overflow-y-auto custom-scrollbar", children: locations.filter(
377
+ (loc) => loc.startsWith(block.text.toUpperCase()) && loc !== block.text.toUpperCase()
378
+ ).map((loc) => /* @__PURE__ */ jsxs(
379
+ "div",
380
+ {
381
+ role: "option",
382
+ className: "group flex items-center justify-between px-4 py-2.5 cursor-pointer transition-all duration-150 hover:bg-slate-50 active:bg-slate-100",
383
+ onMouseDown: (e) => {
384
+ e.preventDefault();
385
+ const element = refs.current[block.id];
386
+ if (element) {
387
+ element.innerText = loc;
388
+ handleBlockTextChange(block.id, loc);
389
+ element.focus();
390
+ const range = document.createRange();
391
+ const sel = window.getSelection();
392
+ range.selectNodeContents(element);
393
+ range.collapse(false);
394
+ sel == null ? void 0 : sel.removeAllRanges();
395
+ sel == null ? void 0 : sel.addRange(range);
396
+ }
397
+ handleBlur(block.id);
398
+ },
399
+ children: [
400
+ /* @__PURE__ */ jsx("span", { className: "text-[12px] font-semibold tracking-wide text-slate-600 uppercase line-clamp-1", children: loc }),
401
+ /* @__PURE__ */ jsx(ArrowRight, { className: "w-3.5 h-3.5 text-slate-300 opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200" })
402
+ ]
403
+ },
404
+ loc
405
+ )) })
406
+ }
407
+ )
408
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
409
+ /* @__PURE__ */ jsx(
410
+ "div",
411
+ {
412
+ ref: (el) => {
413
+ if (!el) return;
414
+ refs.current[block.id] = el;
415
+ },
416
+ contentEditable: true,
417
+ suppressContentEditableWarning: true,
418
+ "aria-label": `${blockStyles[block.type].label} text`,
419
+ "aria-multiline": block.type === "ACTION" || block.type === "DIALOGUE",
420
+ spellCheck: false,
421
+ className: `block outline-none w-full min-h-[2.5rem] px-4 py-2 break-words ${blockStyles[block.type].className}`,
422
+ onInput: (e) => handleBlockTextChange(
423
+ block.id,
424
+ e.target.innerText
425
+ ),
426
+ onKeyDown: (e) => handleKeyDown(e, block.id, block.text),
427
+ onFocus: () => handleFocus(block.id),
428
+ onBlur: () => handleBlur(block.id),
429
+ style: blockStyles[block.type].inputStyle
430
+ }
431
+ ),
432
+ focusedBlockId === block.id && block.type === "CHARACTER" && showSuggestions && characters.length > 0 && /* @__PURE__ */ jsx(
433
+ "div",
434
+ {
435
+ role: "listbox",
436
+ id: `suggestions-${block.id}`,
437
+ className: "absolute top-[calc(100%+8px)] left-1/2 -translate-x-1/2 w-72 z-50 bg-white border border-slate-200 shadow-2xl shadow-slate-200/60 rounded-xl py-2 overflow-hidden animate-in fade-in zoom-in-95 duration-200",
438
+ children: /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto custom-scrollbar", children: characters.filter(
439
+ (char) => char.startsWith(block.text.toUpperCase()) && char !== block.text.toUpperCase()
440
+ ).map((char) => /* @__PURE__ */ jsxs(
441
+ "div",
442
+ {
443
+ role: "option",
444
+ className: "group flex items-center px-4 py-2.5 cursor-pointer transition-colors duration-150 hover:bg-slate-50 active:bg-slate-100",
445
+ onMouseDown: (e) => {
446
+ e.preventDefault();
447
+ const element = refs.current[block.id];
448
+ if (element) {
449
+ element.innerText = char;
450
+ handleBlockTextChange(block.id, char);
451
+ element.focus();
452
+ const range = document.createRange();
453
+ const sel = window.getSelection();
454
+ range.selectNodeContents(element);
455
+ range.collapse(false);
456
+ sel == null ? void 0 : sel.removeAllRanges();
457
+ sel == null ? void 0 : sel.addRange(range);
458
+ }
459
+ handleBlur(block.id);
460
+ },
461
+ children: [
462
+ /* @__PURE__ */ jsx(User, { className: "w-3.5 h-3.5 text-slate-300 group-hover:text-sky-500 transition-colors mr-3" }),
463
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-[11px] font-bold tracking-[0.1em] text-slate-600 uppercase text-left", children: char }),
464
+ /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3 text-slate-200 opacity-0 group-hover:opacity-100 transition-all -translate-x-1 group-hover:translate-x-0" })
465
+ ]
466
+ },
467
+ char
468
+ )) })
469
+ }
470
+ ),
471
+ focusedBlockId === block.id && block.type === "CHARACTER" && showExtensionSuggestions && characterExtensions && /* @__PURE__ */ jsx(
472
+ "div",
473
+ {
474
+ role: "listbox",
475
+ id: `extension-suggestions-${block.id}`,
476
+ className: "absolute top-[calc(100%+8px)] left-1/2 -translate-x-1/2 w-72 z-50 bg-white border border-slate-200 shadow-2xl shadow-slate-200/60 rounded-xl py-2 overflow-hidden animate-in fade-in zoom-in-95 duration-200",
477
+ children: /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto custom-scrollbar", children: characterExtensions.filter((ext) => {
478
+ const openParenIndex = block.text.lastIndexOf("(");
479
+ const query = openParenIndex > -1 ? block.text.substring(openParenIndex + 1).toUpperCase() : "";
480
+ return ext.toUpperCase().includes(query);
481
+ }).map((ext) => /* @__PURE__ */ jsxs(
482
+ "div",
483
+ {
484
+ role: "option",
485
+ className: "group flex items-center px-4 py-2.5 cursor-pointer transition-colors duration-150 hover:bg-slate-50 active:bg-slate-100",
486
+ onMouseDown: (e) => {
487
+ e.preventDefault();
488
+ handleSelectCharacterExtension(ext);
489
+ },
490
+ children: [
491
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-[11px] font-bold tracking-[0.1em] text-slate-600 uppercase text-left", children: ext }),
492
+ /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3 text-slate-200 opacity-0 group-hover:opacity-100 transition-all -translate-x-1 group-hover:translate-x-0" })
493
+ ]
494
+ },
495
+ ext
496
+ )) })
497
+ }
498
+ )
499
+ ] })
500
+ },
501
+ block.id + "-" + block.type
502
+ );
503
+ })
504
+ }
505
+ ) }),
516
506
  /* @__PURE__ */ jsxs("div", { className: "fixed bottom-6 right-6 flex flex-col items-end gap-4 z-50", children: [
517
507
  /* @__PURE__ */ jsx(PdfImporter, { onScriptImported: handleScriptImport, children: /* @__PURE__ */ jsxs(Fragment, { children: [
518
508
  /* @__PURE__ */ jsx(Upload, { className: "w-5 h-5" }),
@@ -544,46 +534,24 @@ function ScreenplayEditorView({
544
534
  ),
545
535
  isRulesOpen && /* @__PURE__ */ jsxs("div", { className: "bg-white/80 backdrop-blur-md rounded-xl shadow-lg border border-zinc-200/50 p-4 text-xs text-zinc-700 select-none font-sans overflow-hidden transition-all duration-300 w-64 origin-bottom-right animate-in fade-in zoom-in-95", children: [
546
536
  /* @__PURE__ */ jsx("h4", { className: "font-bold text-zinc-800 mb-3 text-sm", children: "Settings & Shortcuts" }),
547
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
548
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-6", children: [
549
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-zinc-800", children: "A4 Page Split" }),
550
- /* @__PURE__ */ jsx(
551
- "button",
552
- {
553
- type: "button",
554
- role: "switch",
555
- "aria-checked": isPageSplitEnabled,
556
- onClick: togglePageSplit,
557
- className: `${isPageSplitEnabled ? "bg-zinc-900" : "bg-zinc-300"} relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none`,
558
- children: /* @__PURE__ */ jsx(
559
- "span",
560
- {
561
- "aria-hidden": "true",
562
- className: `${isPageSplitEnabled ? "translate-x-4" : "translate-x-0"} pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out`
563
- }
564
- )
565
- }
566
- )
537
+ /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: /* @__PURE__ */ jsxs("ul", { className: "space-y-1.5", children: [
538
+ /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-6", children: [
539
+ /* @__PURE__ */ jsx("span", { children: "New Block" }),
540
+ /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-semibold text-zinc-800 bg-zinc-200/70 border border-zinc-300/70 rounded-md", children: "Enter" })
567
541
  ] }),
568
- /* @__PURE__ */ jsx("div", { className: "space-y-1.5 pt-3 border-t border-zinc-200/50", children: /* @__PURE__ */ jsxs("ul", { className: "space-y-1.5", children: [
569
- /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-6", children: [
570
- /* @__PURE__ */ jsx("span", { children: "New Block" }),
571
- /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-semibold text-zinc-800 bg-zinc-200/70 border border-zinc-300/70 rounded-md", children: "Enter" })
572
- ] }),
573
- /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-6", children: [
574
- /* @__PURE__ */ jsx("span", { children: "Delete Block" }),
575
- /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-semibold text-zinc-800 bg-zinc-200/70 border border-zinc-300/70 rounded-md", children: "Backspace" })
576
- ] }),
577
- /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-6", children: [
578
- /* @__PURE__ */ jsx("span", { children: "Change Type" }),
579
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
580
- /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-semibold text-zinc-800 bg-zinc-200/70 border border-zinc-300/70 rounded-md", children: "Ctrl" }),
581
- /* @__PURE__ */ jsx("span", { children: "+" }),
582
- /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-semibold text-zinc-800 bg-zinc-200/70 border border-zinc-300/70 rounded-md", children: "\u2191/\u2193" })
583
- ] })
542
+ /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-6", children: [
543
+ /* @__PURE__ */ jsx("span", { children: "Delete Block" }),
544
+ /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-semibold text-zinc-800 bg-zinc-200/70 border border-zinc-300/70 rounded-md", children: "Backspace" })
545
+ ] }),
546
+ /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-6", children: [
547
+ /* @__PURE__ */ jsx("span", { children: "Change Type" }),
548
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
549
+ /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-semibold text-zinc-800 bg-zinc-200/70 border border-zinc-300/70 rounded-md", children: "Ctrl" }),
550
+ /* @__PURE__ */ jsx("span", { children: "+" }),
551
+ /* @__PURE__ */ jsx("kbd", { className: "px-2 py-1 text-xs font-semibold text-zinc-800 bg-zinc-200/70 border border-zinc-300/70 rounded-md", children: "\u2191/\u2193" })
584
552
  ] })
585
- ] }) })
586
- ] })
553
+ ] })
554
+ ] }) }) })
587
555
  ] }),
588
556
  /* @__PURE__ */ jsx(
589
557
  "button",
@@ -689,9 +657,17 @@ function incrementChar(text) {
689
657
  function changeBlockType(blocks, id, newType) {
690
658
  const currentIndex = blocks.findIndex((b) => b.id === id);
691
659
  if (currentIndex === -1) return blocks;
660
+ const currentBlock = blocks[currentIndex];
692
661
  const newBlock = createNewBlock(newType);
662
+ if (newType === "PARENTHETICAL") {
663
+ const cleanText = currentBlock.text.replace(/[()]/g, "");
664
+ newBlock.text = `(${cleanText})`;
665
+ } else {
666
+ newBlock.text = currentBlock.text;
667
+ }
693
668
  if (newType === "SCENE_HEADING") {
694
669
  newBlock.sceneNumber = generateNextSceneNumber(blocks, currentIndex);
670
+ newBlock.text = newBlock.text.toUpperCase();
695
671
  }
696
672
  return blocks.map((b) => b.id === id ? __spreadProps(__spreadValues({}, newBlock), { id: b.id }) : b);
697
673
  }
@@ -863,19 +839,6 @@ function useScreenplayEditor() {
863
839
  const [showSuggestions, setShowSuggestions] = useState(false);
864
840
  const [showExtensionSuggestions, setShowExtensionSuggestions] = useState(false);
865
841
  const blurTimeout = useRef(null);
866
- const [isPageSplitEnabled, setIsPageSplitEnabled] = useState(false);
867
- const [pageBreaks, setPageBreaks] = useState([]);
868
- const focusStateRef = useRef(null);
869
- const togglePageSplit = useCallback(() => {
870
- if (focusedBlockId && document.activeElement && document.activeElement.hasAttribute("contenteditable")) {
871
- const el = refs.current[focusedBlockId];
872
- if (el) {
873
- const offset = getCaretCharacterOffsetWithin(el);
874
- focusStateRef.current = { id: focusedBlockId, offset };
875
- }
876
- }
877
- setIsPageSplitEnabled((prev) => !prev);
878
- }, [focusedBlockId]);
879
842
  const characterExtensions = useMemo(
880
843
  () => ["(V.O.)", "(O.S.)", "(O.C.)", "(SUBTITLE)", "(CONT'D)"],
881
844
  []
@@ -953,11 +916,13 @@ function useScreenplayEditor() {
953
916
  useEffect(() => {
954
917
  blocks.forEach((block) => {
955
918
  const element = refs.current[block.id];
956
- if (element && element.innerText !== block.text && document.activeElement !== element) {
957
- element.innerText = block.text;
919
+ if (element) {
920
+ if (element.innerText !== block.text && document.activeElement !== element) {
921
+ element.innerText = block.text;
922
+ }
958
923
  }
959
924
  });
960
- }, [blocks, isPageSplitEnabled, pageBreaks]);
925
+ }, [blocks]);
961
926
  useEffect(() => {
962
927
  const handleClickOutside = (e) => {
963
928
  const target = e.target;
@@ -975,98 +940,6 @@ function useScreenplayEditor() {
975
940
  document.removeEventListener("mousedown", handleClickOutside);
976
941
  };
977
942
  }, []);
978
- useEffect(() => {
979
- if (!isPageSplitEnabled) {
980
- setPageBreaks([]);
981
- return;
982
- }
983
- const saveFocus = () => {
984
- if (focusedBlockId && document.activeElement && document.activeElement.hasAttribute("contenteditable")) {
985
- const el = refs.current[focusedBlockId];
986
- if (el) {
987
- const offset = getCaretCharacterOffsetWithin(el);
988
- focusStateRef.current = { id: focusedBlockId, offset };
989
- }
990
- } else {
991
- focusStateRef.current = null;
992
- }
993
- };
994
- const timeoutId = setTimeout(() => {
995
- let currentHeight = 0;
996
- let hasSceneOnCurrentPage = false;
997
- const A4_HEIGHT = 960;
998
- const newPageBreaks = [];
999
- blocks.forEach((block) => {
1000
- const el = refs.current[block.id];
1001
- if (el) {
1002
- const measureEl = document.querySelector(`[data-block-id="${block.id}"]`) || el;
1003
- const style = window.getComputedStyle(measureEl);
1004
- const marginTop = parseFloat(style.marginTop) || 0;
1005
- const marginBottom = parseFloat(style.marginBottom) || 0;
1006
- const height = measureEl.getBoundingClientRect().height + marginTop + marginBottom;
1007
- let breakPage = false;
1008
- if (currentHeight + height > A4_HEIGHT) {
1009
- breakPage = true;
1010
- } else if (block.type === "SCENE_HEADING") {
1011
- if (hasSceneOnCurrentPage) {
1012
- breakPage = true;
1013
- } else if (currentHeight > A4_HEIGHT - 120) {
1014
- breakPage = true;
1015
- }
1016
- }
1017
- if (breakPage && currentHeight > 0) {
1018
- newPageBreaks.push(block.id);
1019
- currentHeight = height;
1020
- hasSceneOnCurrentPage = block.type === "SCENE_HEADING";
1021
- } else {
1022
- currentHeight += height;
1023
- if (block.type === "SCENE_HEADING") {
1024
- hasSceneOnCurrentPage = true;
1025
- }
1026
- }
1027
- }
1028
- });
1029
- setPageBreaks((prev) => {
1030
- if (prev.length !== newPageBreaks.length) {
1031
- saveFocus();
1032
- return newPageBreaks;
1033
- }
1034
- for (let i = 0; i < prev.length; i++) {
1035
- if (prev[i] !== newPageBreaks[i]) {
1036
- saveFocus();
1037
- return newPageBreaks;
1038
- }
1039
- }
1040
- return prev;
1041
- });
1042
- }, 300);
1043
- return () => clearTimeout(timeoutId);
1044
- }, [blocks, isPageSplitEnabled, focusedBlockId]);
1045
- const pages = useMemo(() => {
1046
- if (!isPageSplitEnabled || pageBreaks.length === 0) return [blocks];
1047
- const result = [];
1048
- let currentPage = [];
1049
- for (const block of blocks) {
1050
- if (pageBreaks.includes(block.id) && currentPage.length > 0) {
1051
- result.push(currentPage);
1052
- currentPage = [];
1053
- }
1054
- currentPage.push(block);
1055
- }
1056
- if (currentPage.length > 0) result.push(currentPage);
1057
- return result;
1058
- }, [blocks, isPageSplitEnabled, pageBreaks]);
1059
- useEffect(() => {
1060
- if (focusStateRef.current) {
1061
- const { id, offset } = focusStateRef.current;
1062
- const el = refs.current[id];
1063
- if (el && document.activeElement !== el) {
1064
- el.focus();
1065
- setCaretPosition(el, offset);
1066
- }
1067
- focusStateRef.current = null;
1068
- }
1069
- }, [pages]);
1070
943
  const handleBlockTextChange = useCallback(
1071
944
  (id, text) => {
1072
945
  const block = blocks.find((b) => b.id === id);
@@ -1137,17 +1010,15 @@ function useScreenplayEditor() {
1137
1010
  const el = refs.current[focusedBlockId];
1138
1011
  if (el) {
1139
1012
  el.focus();
1140
- const newBlock = createNewBlock(newType);
1141
- el.innerText = newBlock.text;
1142
- if (newType === "PARENTHETICAL") {
1143
- setCaretPosition(el, 1);
1144
- } else {
1145
- setCaretPosition(el, newBlock.text.length);
1013
+ const currentBlock = blocks.find((b) => b.id === focusedBlockId);
1014
+ if (currentBlock) {
1015
+ const pos = newType === "PARENTHETICAL" ? el.innerText.length - 1 : el.innerText.length;
1016
+ setCaretPosition(el, Math.max(0, pos));
1146
1017
  }
1147
1018
  }
1148
- }, 0);
1019
+ }, 10);
1149
1020
  },
1150
- [focusedBlockId]
1021
+ [focusedBlockId, blocks]
1151
1022
  );
1152
1023
  const handleSelectCharacterExtension = useCallback(
1153
1024
  (extension) => {
@@ -1203,8 +1074,6 @@ function useScreenplayEditor() {
1203
1074
  const el = refs.current[id];
1204
1075
  if (el) {
1205
1076
  el.focus();
1206
- const newBlock = createNewBlock(newType);
1207
- el.innerText = newBlock.text;
1208
1077
  if (newType === "PARENTHETICAL") {
1209
1078
  setCaretPosition(el, 1);
1210
1079
  } else {
@@ -1400,9 +1269,6 @@ function useScreenplayEditor() {
1400
1269
  }, []);
1401
1270
  return {
1402
1271
  blocks,
1403
- pages,
1404
- isPageSplitEnabled,
1405
- togglePageSplit,
1406
1272
  refs,
1407
1273
  focusedBlockId,
1408
1274
  showSuggestions,