@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.cjs +227 -361
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -4
- package/dist/index.d.ts +1 -4
- package/dist/index.js +228 -362
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -242,9 +242,6 @@ function PdfImporter({ onScriptImported, children }) {
|
|
|
242
242
|
}
|
|
243
243
|
function ScreenplayEditorView({
|
|
244
244
|
blocks,
|
|
245
|
-
pages,
|
|
246
|
-
isPageSplitEnabled,
|
|
247
|
-
togglePageSplit,
|
|
248
245
|
refs,
|
|
249
246
|
focusedBlockId,
|
|
250
247
|
showSuggestions,
|
|
@@ -307,131 +304,56 @@ function ScreenplayEditorView({
|
|
|
307
304
|
type
|
|
308
305
|
);
|
|
309
306
|
}) }),
|
|
310
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-12 w-full items-center pb-24", children:
|
|
307
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-12 w-full items-center pb-24", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
311
308
|
"div",
|
|
312
309
|
{
|
|
313
310
|
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",
|
|
314
311
|
style: {
|
|
315
312
|
fontFamily: "var(--font-courier-prime, 'Courier New', Courier, monospace)"
|
|
316
313
|
},
|
|
317
|
-
children:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
/* @__PURE__ */ jsxRuntime.
|
|
327
|
-
|
|
328
|
-
"input",
|
|
329
|
-
{
|
|
330
|
-
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",
|
|
331
|
-
spellCheck: false,
|
|
332
|
-
value: block.sceneNumber || "",
|
|
333
|
-
onChange: (e) => handleSceneNumberChange(
|
|
334
|
-
block.id,
|
|
335
|
-
e.target.value.toUpperCase()
|
|
336
|
-
),
|
|
337
|
-
onFocus: () => handleFocus(block.id),
|
|
338
|
-
onBlur: () => handleBlur(block.id),
|
|
339
|
-
onKeyDown: (e) => {
|
|
340
|
-
if (e.key === "Enter" || e.key === "Backspace") {
|
|
341
|
-
e.stopPropagation();
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
"aria-label": "Scene Number"
|
|
345
|
-
}
|
|
346
|
-
),
|
|
347
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
348
|
-
"select",
|
|
349
|
-
{
|
|
350
|
-
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",
|
|
351
|
-
"aria-label": "Scene Type",
|
|
352
|
-
value: (_a = block.sceneType) != null ? _a : "INT.",
|
|
353
|
-
onChange: (e) => handleSceneTypeChange(block.id, e.target.value),
|
|
354
|
-
children: [
|
|
355
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { children: "INT." }),
|
|
356
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { children: "EXT." }),
|
|
357
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { children: "INT/EXT." })
|
|
358
|
-
]
|
|
359
|
-
}
|
|
360
|
-
),
|
|
361
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
362
|
-
"div",
|
|
363
|
-
{
|
|
364
|
-
ref: (el) => {
|
|
365
|
-
if (!el) return;
|
|
366
|
-
refs.current[block.id] = el;
|
|
367
|
-
},
|
|
368
|
-
contentEditable: true,
|
|
369
|
-
suppressContentEditableWarning: true,
|
|
370
|
-
"aria-label": `Scene Heading: ${block.text}`,
|
|
371
|
-
"aria-haspopup": "listbox",
|
|
372
|
-
"aria-expanded": focusedBlockId === block.id && showSuggestions && locations.length > 0,
|
|
373
|
-
spellCheck: false,
|
|
374
|
-
className: "min-w-[3rem] py-1 outline-none text-base font-bold uppercase tracking-widest break-all bg-transparent",
|
|
375
|
-
onInput: (e) => handleBlockTextChange(
|
|
376
|
-
block.id,
|
|
377
|
-
e.target.innerText
|
|
378
|
-
),
|
|
379
|
-
onKeyDown: (e) => handleKeyDown(e, block.id, block.text),
|
|
380
|
-
onFocus: () => handleFocus(block.id),
|
|
381
|
-
onBlur: () => handleBlur(block.id)
|
|
382
|
-
}
|
|
383
|
-
),
|
|
384
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-zinc-400/80 font-bold", children: "-" }),
|
|
385
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
386
|
-
"select",
|
|
387
|
-
{
|
|
388
|
-
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",
|
|
389
|
-
"aria-label": "Time of Day",
|
|
390
|
-
value: (_b = block.timeOfDay) != null ? _b : "DAY",
|
|
391
|
-
onChange: (e) => handleTimeOfDayChange(block.id, e.target.value),
|
|
392
|
-
children: timeOfDayOptions.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: t }, t))
|
|
393
|
-
}
|
|
394
|
-
)
|
|
395
|
-
] }),
|
|
396
|
-
focusedBlockId === block.id && showSuggestions && locations.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
397
|
-
"div",
|
|
314
|
+
children: blocks.map((block) => {
|
|
315
|
+
var _a, _b;
|
|
316
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
317
|
+
"div",
|
|
318
|
+
{
|
|
319
|
+
"data-block-id": block.id,
|
|
320
|
+
className: `relative rounded-sm transition-all duration-200 outline-none ${focusedBlockId === block.id ? "bg-zinc-100/50" : "bg-transparent"}`,
|
|
321
|
+
children: block.type === "SCENE_HEADING" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
322
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-4 py-1 bg-transparent", children: [
|
|
323
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
324
|
+
"input",
|
|
398
325
|
{
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
handleBlockTextChange(block.id, loc);
|
|
415
|
-
element.focus();
|
|
416
|
-
const range = document.createRange();
|
|
417
|
-
const sel = window.getSelection();
|
|
418
|
-
range.selectNodeContents(element);
|
|
419
|
-
range.collapse(false);
|
|
420
|
-
sel == null ? void 0 : sel.removeAllRanges();
|
|
421
|
-
sel == null ? void 0 : sel.addRange(range);
|
|
422
|
-
}
|
|
423
|
-
handleBlur(block.id);
|
|
424
|
-
},
|
|
425
|
-
children: [
|
|
426
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[12px] font-semibold tracking-wide text-slate-600 uppercase line-clamp-1", children: loc }),
|
|
427
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.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" })
|
|
428
|
-
]
|
|
429
|
-
},
|
|
430
|
-
loc
|
|
431
|
-
)) })
|
|
326
|
+
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",
|
|
327
|
+
spellCheck: false,
|
|
328
|
+
value: block.sceneNumber || "",
|
|
329
|
+
onChange: (e) => handleSceneNumberChange(
|
|
330
|
+
block.id,
|
|
331
|
+
e.target.value.toUpperCase()
|
|
332
|
+
),
|
|
333
|
+
onFocus: () => handleFocus(block.id),
|
|
334
|
+
onBlur: () => handleBlur(block.id),
|
|
335
|
+
onKeyDown: (e) => {
|
|
336
|
+
if (e.key === "Enter" || e.key === "Backspace") {
|
|
337
|
+
e.stopPropagation();
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
"aria-label": "Scene Number"
|
|
432
341
|
}
|
|
433
|
-
)
|
|
434
|
-
|
|
342
|
+
),
|
|
343
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
344
|
+
"select",
|
|
345
|
+
{
|
|
346
|
+
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",
|
|
347
|
+
"aria-label": "Scene Type",
|
|
348
|
+
value: (_a = block.sceneType) != null ? _a : "INT.",
|
|
349
|
+
onChange: (e) => handleSceneTypeChange(block.id, e.target.value),
|
|
350
|
+
children: [
|
|
351
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { children: "INT." }),
|
|
352
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { children: "EXT." }),
|
|
353
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { children: "INT/EXT." })
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
),
|
|
435
357
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
436
358
|
"div",
|
|
437
359
|
{
|
|
@@ -441,100 +363,168 @@ function ScreenplayEditorView({
|
|
|
441
363
|
},
|
|
442
364
|
contentEditable: true,
|
|
443
365
|
suppressContentEditableWarning: true,
|
|
444
|
-
"aria-label":
|
|
445
|
-
"aria-
|
|
366
|
+
"aria-label": `Scene Heading: ${block.text}`,
|
|
367
|
+
"aria-haspopup": "listbox",
|
|
368
|
+
"aria-expanded": focusedBlockId === block.id && showSuggestions && locations.length > 0,
|
|
446
369
|
spellCheck: false,
|
|
447
|
-
className:
|
|
370
|
+
className: "min-w-[3rem] py-1 outline-none text-base font-bold uppercase tracking-widest break-all bg-transparent",
|
|
448
371
|
onInput: (e) => handleBlockTextChange(
|
|
449
372
|
block.id,
|
|
450
373
|
e.target.innerText
|
|
451
374
|
),
|
|
452
375
|
onKeyDown: (e) => handleKeyDown(e, block.id, block.text),
|
|
453
376
|
onFocus: () => handleFocus(block.id),
|
|
454
|
-
onBlur: () => handleBlur(block.id)
|
|
455
|
-
style: blockStyles[block.type].inputStyle
|
|
456
|
-
}
|
|
457
|
-
),
|
|
458
|
-
focusedBlockId === block.id && block.type === "CHARACTER" && showSuggestions && characters.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
459
|
-
"div",
|
|
460
|
-
{
|
|
461
|
-
role: "listbox",
|
|
462
|
-
id: `suggestions-${block.id}`,
|
|
463
|
-
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",
|
|
464
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 overflow-y-auto custom-scrollbar", children: characters.filter(
|
|
465
|
-
(char) => char.startsWith(block.text.toUpperCase()) && char !== block.text.toUpperCase()
|
|
466
|
-
).map((char) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
467
|
-
"div",
|
|
468
|
-
{
|
|
469
|
-
role: "option",
|
|
470
|
-
className: "group flex items-center px-4 py-2.5 cursor-pointer transition-colors duration-150 hover:bg-slate-50 active:bg-slate-100",
|
|
471
|
-
onMouseDown: (e) => {
|
|
472
|
-
e.preventDefault();
|
|
473
|
-
const element = refs.current[block.id];
|
|
474
|
-
if (element) {
|
|
475
|
-
element.innerText = char;
|
|
476
|
-
handleBlockTextChange(block.id, char);
|
|
477
|
-
element.focus();
|
|
478
|
-
const range = document.createRange();
|
|
479
|
-
const sel = window.getSelection();
|
|
480
|
-
range.selectNodeContents(element);
|
|
481
|
-
range.collapse(false);
|
|
482
|
-
sel == null ? void 0 : sel.removeAllRanges();
|
|
483
|
-
sel == null ? void 0 : sel.addRange(range);
|
|
484
|
-
}
|
|
485
|
-
handleBlur(block.id);
|
|
486
|
-
},
|
|
487
|
-
children: [
|
|
488
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "w-3.5 h-3.5 text-slate-300 group-hover:text-sky-500 transition-colors mr-3" }),
|
|
489
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-[11px] font-bold tracking-[0.1em] text-slate-600 uppercase text-left", children: char }),
|
|
490
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.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" })
|
|
491
|
-
]
|
|
492
|
-
},
|
|
493
|
-
char
|
|
494
|
-
)) })
|
|
377
|
+
onBlur: () => handleBlur(block.id)
|
|
495
378
|
}
|
|
496
379
|
),
|
|
497
|
-
|
|
498
|
-
|
|
380
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-zinc-400/80 font-bold", children: "-" }),
|
|
381
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
382
|
+
"select",
|
|
499
383
|
{
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const query = openParenIndex > -1 ? block.text.substring(openParenIndex + 1).toUpperCase() : "";
|
|
506
|
-
return ext.toUpperCase().includes(query);
|
|
507
|
-
}).map((ext) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
508
|
-
"div",
|
|
509
|
-
{
|
|
510
|
-
role: "option",
|
|
511
|
-
className: "group flex items-center px-4 py-2.5 cursor-pointer transition-colors duration-150 hover:bg-slate-50 active:bg-slate-100",
|
|
512
|
-
onMouseDown: (e) => {
|
|
513
|
-
e.preventDefault();
|
|
514
|
-
handleSelectCharacterExtension(ext);
|
|
515
|
-
},
|
|
516
|
-
children: [
|
|
517
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-[11px] font-bold tracking-[0.1em] text-slate-600 uppercase text-left", children: ext }),
|
|
518
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.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" })
|
|
519
|
-
]
|
|
520
|
-
},
|
|
521
|
-
ext
|
|
522
|
-
)) })
|
|
384
|
+
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",
|
|
385
|
+
"aria-label": "Time of Day",
|
|
386
|
+
value: (_b = block.timeOfDay) != null ? _b : "DAY",
|
|
387
|
+
onChange: (e) => handleTimeOfDayChange(block.id, e.target.value),
|
|
388
|
+
children: timeOfDayOptions.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: t }, t))
|
|
523
389
|
}
|
|
524
390
|
)
|
|
525
|
-
] })
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
391
|
+
] }),
|
|
392
|
+
focusedBlockId === block.id && showSuggestions && locations.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
393
|
+
"div",
|
|
394
|
+
{
|
|
395
|
+
role: "listbox",
|
|
396
|
+
id: `suggestions-${block.id}`,
|
|
397
|
+
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",
|
|
398
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-60 overflow-y-auto custom-scrollbar", children: locations.filter(
|
|
399
|
+
(loc) => loc.startsWith(block.text.toUpperCase()) && loc !== block.text.toUpperCase()
|
|
400
|
+
).map((loc) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
401
|
+
"div",
|
|
402
|
+
{
|
|
403
|
+
role: "option",
|
|
404
|
+
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",
|
|
405
|
+
onMouseDown: (e) => {
|
|
406
|
+
e.preventDefault();
|
|
407
|
+
const element = refs.current[block.id];
|
|
408
|
+
if (element) {
|
|
409
|
+
element.innerText = loc;
|
|
410
|
+
handleBlockTextChange(block.id, loc);
|
|
411
|
+
element.focus();
|
|
412
|
+
const range = document.createRange();
|
|
413
|
+
const sel = window.getSelection();
|
|
414
|
+
range.selectNodeContents(element);
|
|
415
|
+
range.collapse(false);
|
|
416
|
+
sel == null ? void 0 : sel.removeAllRanges();
|
|
417
|
+
sel == null ? void 0 : sel.addRange(range);
|
|
418
|
+
}
|
|
419
|
+
handleBlur(block.id);
|
|
420
|
+
},
|
|
421
|
+
children: [
|
|
422
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[12px] font-semibold tracking-wide text-slate-600 uppercase line-clamp-1", children: loc }),
|
|
423
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.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" })
|
|
424
|
+
]
|
|
425
|
+
},
|
|
426
|
+
loc
|
|
427
|
+
)) })
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
431
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
432
|
+
"div",
|
|
433
|
+
{
|
|
434
|
+
ref: (el) => {
|
|
435
|
+
if (!el) return;
|
|
436
|
+
refs.current[block.id] = el;
|
|
437
|
+
},
|
|
438
|
+
contentEditable: true,
|
|
439
|
+
suppressContentEditableWarning: true,
|
|
440
|
+
"aria-label": `${blockStyles[block.type].label} text`,
|
|
441
|
+
"aria-multiline": block.type === "ACTION" || block.type === "DIALOGUE",
|
|
442
|
+
spellCheck: false,
|
|
443
|
+
className: `block outline-none w-full min-h-[2.5rem] px-4 py-2 break-words ${blockStyles[block.type].className}`,
|
|
444
|
+
onInput: (e) => handleBlockTextChange(
|
|
445
|
+
block.id,
|
|
446
|
+
e.target.innerText
|
|
447
|
+
),
|
|
448
|
+
onKeyDown: (e) => handleKeyDown(e, block.id, block.text),
|
|
449
|
+
onFocus: () => handleFocus(block.id),
|
|
450
|
+
onBlur: () => handleBlur(block.id),
|
|
451
|
+
style: blockStyles[block.type].inputStyle
|
|
452
|
+
}
|
|
453
|
+
),
|
|
454
|
+
focusedBlockId === block.id && block.type === "CHARACTER" && showSuggestions && characters.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
455
|
+
"div",
|
|
456
|
+
{
|
|
457
|
+
role: "listbox",
|
|
458
|
+
id: `suggestions-${block.id}`,
|
|
459
|
+
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",
|
|
460
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 overflow-y-auto custom-scrollbar", children: characters.filter(
|
|
461
|
+
(char) => char.startsWith(block.text.toUpperCase()) && char !== block.text.toUpperCase()
|
|
462
|
+
).map((char) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
463
|
+
"div",
|
|
464
|
+
{
|
|
465
|
+
role: "option",
|
|
466
|
+
className: "group flex items-center px-4 py-2.5 cursor-pointer transition-colors duration-150 hover:bg-slate-50 active:bg-slate-100",
|
|
467
|
+
onMouseDown: (e) => {
|
|
468
|
+
e.preventDefault();
|
|
469
|
+
const element = refs.current[block.id];
|
|
470
|
+
if (element) {
|
|
471
|
+
element.innerText = char;
|
|
472
|
+
handleBlockTextChange(block.id, char);
|
|
473
|
+
element.focus();
|
|
474
|
+
const range = document.createRange();
|
|
475
|
+
const sel = window.getSelection();
|
|
476
|
+
range.selectNodeContents(element);
|
|
477
|
+
range.collapse(false);
|
|
478
|
+
sel == null ? void 0 : sel.removeAllRanges();
|
|
479
|
+
sel == null ? void 0 : sel.addRange(range);
|
|
480
|
+
}
|
|
481
|
+
handleBlur(block.id);
|
|
482
|
+
},
|
|
483
|
+
children: [
|
|
484
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "w-3.5 h-3.5 text-slate-300 group-hover:text-sky-500 transition-colors mr-3" }),
|
|
485
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-[11px] font-bold tracking-[0.1em] text-slate-600 uppercase text-left", children: char }),
|
|
486
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.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" })
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
char
|
|
490
|
+
)) })
|
|
491
|
+
}
|
|
492
|
+
),
|
|
493
|
+
focusedBlockId === block.id && block.type === "CHARACTER" && showExtensionSuggestions && characterExtensions && /* @__PURE__ */ jsxRuntime.jsx(
|
|
494
|
+
"div",
|
|
495
|
+
{
|
|
496
|
+
role: "listbox",
|
|
497
|
+
id: `extension-suggestions-${block.id}`,
|
|
498
|
+
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",
|
|
499
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 overflow-y-auto custom-scrollbar", children: characterExtensions.filter((ext) => {
|
|
500
|
+
const openParenIndex = block.text.lastIndexOf("(");
|
|
501
|
+
const query = openParenIndex > -1 ? block.text.substring(openParenIndex + 1).toUpperCase() : "";
|
|
502
|
+
return ext.toUpperCase().includes(query);
|
|
503
|
+
}).map((ext) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
504
|
+
"div",
|
|
505
|
+
{
|
|
506
|
+
role: "option",
|
|
507
|
+
className: "group flex items-center px-4 py-2.5 cursor-pointer transition-colors duration-150 hover:bg-slate-50 active:bg-slate-100",
|
|
508
|
+
onMouseDown: (e) => {
|
|
509
|
+
e.preventDefault();
|
|
510
|
+
handleSelectCharacterExtension(ext);
|
|
511
|
+
},
|
|
512
|
+
children: [
|
|
513
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-[11px] font-bold tracking-[0.1em] text-slate-600 uppercase text-left", children: ext }),
|
|
514
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.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" })
|
|
515
|
+
]
|
|
516
|
+
},
|
|
517
|
+
ext
|
|
518
|
+
)) })
|
|
519
|
+
}
|
|
520
|
+
)
|
|
521
|
+
] })
|
|
522
|
+
},
|
|
523
|
+
block.id + "-" + block.type
|
|
524
|
+
);
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
) }),
|
|
538
528
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed bottom-6 right-6 flex flex-col items-end gap-4 z-50", children: [
|
|
539
529
|
/* @__PURE__ */ jsxRuntime.jsx(PdfImporter, { onScriptImported: handleScriptImport, children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
540
530
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "w-5 h-5" }),
|
|
@@ -566,46 +556,24 @@ function ScreenplayEditorView({
|
|
|
566
556
|
),
|
|
567
557
|
isRulesOpen && /* @__PURE__ */ jsxRuntime.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: [
|
|
568
558
|
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-bold text-zinc-800 mb-3 text-sm", children: "Settings & Shortcuts" }),
|
|
569
|
-
/* @__PURE__ */ jsxRuntime.
|
|
570
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
571
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", {
|
|
572
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
573
|
-
"button",
|
|
574
|
-
{
|
|
575
|
-
type: "button",
|
|
576
|
-
role: "switch",
|
|
577
|
-
"aria-checked": isPageSplitEnabled,
|
|
578
|
-
onClick: togglePageSplit,
|
|
579
|
-
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`,
|
|
580
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
581
|
-
"span",
|
|
582
|
-
{
|
|
583
|
-
"aria-hidden": "true",
|
|
584
|
-
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`
|
|
585
|
-
}
|
|
586
|
-
)
|
|
587
|
-
}
|
|
588
|
-
)
|
|
559
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-1.5", children: [
|
|
560
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between gap-6", children: [
|
|
561
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "New Block" }),
|
|
562
|
+
/* @__PURE__ */ jsxRuntime.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" })
|
|
589
563
|
] }),
|
|
590
|
-
/* @__PURE__ */ jsxRuntime.
|
|
591
|
-
/* @__PURE__ */ jsxRuntime.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
/* @__PURE__ */ jsxRuntime.
|
|
596
|
-
|
|
597
|
-
/* @__PURE__ */ jsxRuntime.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: "
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Change Type" }),
|
|
601
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
602
|
-
/* @__PURE__ */ jsxRuntime.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" }),
|
|
603
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "+" }),
|
|
604
|
-
/* @__PURE__ */ jsxRuntime.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" })
|
|
605
|
-
] })
|
|
564
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between gap-6", children: [
|
|
565
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Delete Block" }),
|
|
566
|
+
/* @__PURE__ */ jsxRuntime.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" })
|
|
567
|
+
] }),
|
|
568
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center justify-between gap-6", children: [
|
|
569
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Change Type" }),
|
|
570
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
571
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
572
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "+" }),
|
|
573
|
+
/* @__PURE__ */ jsxRuntime.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" })
|
|
606
574
|
] })
|
|
607
|
-
] })
|
|
608
|
-
] })
|
|
575
|
+
] })
|
|
576
|
+
] }) }) })
|
|
609
577
|
] }),
|
|
610
578
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
611
579
|
"button",
|
|
@@ -711,9 +679,17 @@ function incrementChar(text) {
|
|
|
711
679
|
function changeBlockType(blocks, id, newType) {
|
|
712
680
|
const currentIndex = blocks.findIndex((b) => b.id === id);
|
|
713
681
|
if (currentIndex === -1) return blocks;
|
|
682
|
+
const currentBlock = blocks[currentIndex];
|
|
714
683
|
const newBlock = createNewBlock(newType);
|
|
684
|
+
if (newType === "PARENTHETICAL") {
|
|
685
|
+
const cleanText = currentBlock.text.replace(/[()]/g, "");
|
|
686
|
+
newBlock.text = `(${cleanText})`;
|
|
687
|
+
} else {
|
|
688
|
+
newBlock.text = currentBlock.text;
|
|
689
|
+
}
|
|
715
690
|
if (newType === "SCENE_HEADING") {
|
|
716
691
|
newBlock.sceneNumber = generateNextSceneNumber(blocks, currentIndex);
|
|
692
|
+
newBlock.text = newBlock.text.toUpperCase();
|
|
717
693
|
}
|
|
718
694
|
return blocks.map((b) => b.id === id ? __spreadProps(__spreadValues({}, newBlock), { id: b.id }) : b);
|
|
719
695
|
}
|
|
@@ -885,19 +861,6 @@ function useScreenplayEditor() {
|
|
|
885
861
|
const [showSuggestions, setShowSuggestions] = react.useState(false);
|
|
886
862
|
const [showExtensionSuggestions, setShowExtensionSuggestions] = react.useState(false);
|
|
887
863
|
const blurTimeout = react.useRef(null);
|
|
888
|
-
const [isPageSplitEnabled, setIsPageSplitEnabled] = react.useState(false);
|
|
889
|
-
const [pageBreaks, setPageBreaks] = react.useState([]);
|
|
890
|
-
const focusStateRef = react.useRef(null);
|
|
891
|
-
const togglePageSplit = react.useCallback(() => {
|
|
892
|
-
if (focusedBlockId && document.activeElement && document.activeElement.hasAttribute("contenteditable")) {
|
|
893
|
-
const el = refs.current[focusedBlockId];
|
|
894
|
-
if (el) {
|
|
895
|
-
const offset = getCaretCharacterOffsetWithin(el);
|
|
896
|
-
focusStateRef.current = { id: focusedBlockId, offset };
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
setIsPageSplitEnabled((prev) => !prev);
|
|
900
|
-
}, [focusedBlockId]);
|
|
901
864
|
const characterExtensions = react.useMemo(
|
|
902
865
|
() => ["(V.O.)", "(O.S.)", "(O.C.)", "(SUBTITLE)", "(CONT'D)"],
|
|
903
866
|
[]
|
|
@@ -975,11 +938,13 @@ function useScreenplayEditor() {
|
|
|
975
938
|
react.useEffect(() => {
|
|
976
939
|
blocks.forEach((block) => {
|
|
977
940
|
const element = refs.current[block.id];
|
|
978
|
-
if (element
|
|
979
|
-
element.innerText
|
|
941
|
+
if (element) {
|
|
942
|
+
if (element.innerText !== block.text && document.activeElement !== element) {
|
|
943
|
+
element.innerText = block.text;
|
|
944
|
+
}
|
|
980
945
|
}
|
|
981
946
|
});
|
|
982
|
-
}, [blocks
|
|
947
|
+
}, [blocks]);
|
|
983
948
|
react.useEffect(() => {
|
|
984
949
|
const handleClickOutside = (e) => {
|
|
985
950
|
const target = e.target;
|
|
@@ -997,98 +962,6 @@ function useScreenplayEditor() {
|
|
|
997
962
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
998
963
|
};
|
|
999
964
|
}, []);
|
|
1000
|
-
react.useEffect(() => {
|
|
1001
|
-
if (!isPageSplitEnabled) {
|
|
1002
|
-
setPageBreaks([]);
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
const saveFocus = () => {
|
|
1006
|
-
if (focusedBlockId && document.activeElement && document.activeElement.hasAttribute("contenteditable")) {
|
|
1007
|
-
const el = refs.current[focusedBlockId];
|
|
1008
|
-
if (el) {
|
|
1009
|
-
const offset = getCaretCharacterOffsetWithin(el);
|
|
1010
|
-
focusStateRef.current = { id: focusedBlockId, offset };
|
|
1011
|
-
}
|
|
1012
|
-
} else {
|
|
1013
|
-
focusStateRef.current = null;
|
|
1014
|
-
}
|
|
1015
|
-
};
|
|
1016
|
-
const timeoutId = setTimeout(() => {
|
|
1017
|
-
let currentHeight = 0;
|
|
1018
|
-
let hasSceneOnCurrentPage = false;
|
|
1019
|
-
const A4_HEIGHT = 960;
|
|
1020
|
-
const newPageBreaks = [];
|
|
1021
|
-
blocks.forEach((block) => {
|
|
1022
|
-
const el = refs.current[block.id];
|
|
1023
|
-
if (el) {
|
|
1024
|
-
const measureEl = document.querySelector(`[data-block-id="${block.id}"]`) || el;
|
|
1025
|
-
const style = window.getComputedStyle(measureEl);
|
|
1026
|
-
const marginTop = parseFloat(style.marginTop) || 0;
|
|
1027
|
-
const marginBottom = parseFloat(style.marginBottom) || 0;
|
|
1028
|
-
const height = measureEl.getBoundingClientRect().height + marginTop + marginBottom;
|
|
1029
|
-
let breakPage = false;
|
|
1030
|
-
if (currentHeight + height > A4_HEIGHT) {
|
|
1031
|
-
breakPage = true;
|
|
1032
|
-
} else if (block.type === "SCENE_HEADING") {
|
|
1033
|
-
if (hasSceneOnCurrentPage) {
|
|
1034
|
-
breakPage = true;
|
|
1035
|
-
} else if (currentHeight > A4_HEIGHT - 120) {
|
|
1036
|
-
breakPage = true;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
if (breakPage && currentHeight > 0) {
|
|
1040
|
-
newPageBreaks.push(block.id);
|
|
1041
|
-
currentHeight = height;
|
|
1042
|
-
hasSceneOnCurrentPage = block.type === "SCENE_HEADING";
|
|
1043
|
-
} else {
|
|
1044
|
-
currentHeight += height;
|
|
1045
|
-
if (block.type === "SCENE_HEADING") {
|
|
1046
|
-
hasSceneOnCurrentPage = true;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
});
|
|
1051
|
-
setPageBreaks((prev) => {
|
|
1052
|
-
if (prev.length !== newPageBreaks.length) {
|
|
1053
|
-
saveFocus();
|
|
1054
|
-
return newPageBreaks;
|
|
1055
|
-
}
|
|
1056
|
-
for (let i = 0; i < prev.length; i++) {
|
|
1057
|
-
if (prev[i] !== newPageBreaks[i]) {
|
|
1058
|
-
saveFocus();
|
|
1059
|
-
return newPageBreaks;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
return prev;
|
|
1063
|
-
});
|
|
1064
|
-
}, 300);
|
|
1065
|
-
return () => clearTimeout(timeoutId);
|
|
1066
|
-
}, [blocks, isPageSplitEnabled, focusedBlockId]);
|
|
1067
|
-
const pages = react.useMemo(() => {
|
|
1068
|
-
if (!isPageSplitEnabled || pageBreaks.length === 0) return [blocks];
|
|
1069
|
-
const result = [];
|
|
1070
|
-
let currentPage = [];
|
|
1071
|
-
for (const block of blocks) {
|
|
1072
|
-
if (pageBreaks.includes(block.id) && currentPage.length > 0) {
|
|
1073
|
-
result.push(currentPage);
|
|
1074
|
-
currentPage = [];
|
|
1075
|
-
}
|
|
1076
|
-
currentPage.push(block);
|
|
1077
|
-
}
|
|
1078
|
-
if (currentPage.length > 0) result.push(currentPage);
|
|
1079
|
-
return result;
|
|
1080
|
-
}, [blocks, isPageSplitEnabled, pageBreaks]);
|
|
1081
|
-
react.useEffect(() => {
|
|
1082
|
-
if (focusStateRef.current) {
|
|
1083
|
-
const { id, offset } = focusStateRef.current;
|
|
1084
|
-
const el = refs.current[id];
|
|
1085
|
-
if (el && document.activeElement !== el) {
|
|
1086
|
-
el.focus();
|
|
1087
|
-
setCaretPosition(el, offset);
|
|
1088
|
-
}
|
|
1089
|
-
focusStateRef.current = null;
|
|
1090
|
-
}
|
|
1091
|
-
}, [pages]);
|
|
1092
965
|
const handleBlockTextChange = react.useCallback(
|
|
1093
966
|
(id, text) => {
|
|
1094
967
|
const block = blocks.find((b) => b.id === id);
|
|
@@ -1159,17 +1032,15 @@ function useScreenplayEditor() {
|
|
|
1159
1032
|
const el = refs.current[focusedBlockId];
|
|
1160
1033
|
if (el) {
|
|
1161
1034
|
el.focus();
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
setCaretPosition(el,
|
|
1166
|
-
} else {
|
|
1167
|
-
setCaretPosition(el, newBlock.text.length);
|
|
1035
|
+
const currentBlock = blocks.find((b) => b.id === focusedBlockId);
|
|
1036
|
+
if (currentBlock) {
|
|
1037
|
+
const pos = newType === "PARENTHETICAL" ? el.innerText.length - 1 : el.innerText.length;
|
|
1038
|
+
setCaretPosition(el, Math.max(0, pos));
|
|
1168
1039
|
}
|
|
1169
1040
|
}
|
|
1170
|
-
},
|
|
1041
|
+
}, 10);
|
|
1171
1042
|
},
|
|
1172
|
-
[focusedBlockId]
|
|
1043
|
+
[focusedBlockId, blocks]
|
|
1173
1044
|
);
|
|
1174
1045
|
const handleSelectCharacterExtension = react.useCallback(
|
|
1175
1046
|
(extension) => {
|
|
@@ -1225,8 +1096,6 @@ function useScreenplayEditor() {
|
|
|
1225
1096
|
const el = refs.current[id];
|
|
1226
1097
|
if (el) {
|
|
1227
1098
|
el.focus();
|
|
1228
|
-
const newBlock = createNewBlock(newType);
|
|
1229
|
-
el.innerText = newBlock.text;
|
|
1230
1099
|
if (newType === "PARENTHETICAL") {
|
|
1231
1100
|
setCaretPosition(el, 1);
|
|
1232
1101
|
} else {
|
|
@@ -1422,9 +1291,6 @@ function useScreenplayEditor() {
|
|
|
1422
1291
|
}, []);
|
|
1423
1292
|
return {
|
|
1424
1293
|
blocks,
|
|
1425
|
-
pages,
|
|
1426
|
-
isPageSplitEnabled,
|
|
1427
|
-
togglePageSplit,
|
|
1428
1294
|
refs,
|
|
1429
1295
|
focusedBlockId,
|
|
1430
1296
|
showSuggestions,
|