@vishu1301/script-writing 0.4.2 → 0.4.4

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,6 +1,7 @@
1
1
  import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
2
- import { ArrowRightLeft, MessageCircle, Brackets, UserRound, Sparkles, Clapperboard, ArrowRight, User, ChevronRight, Save, FileDown, Cog } from 'lucide-react';
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
+ import * as pdfjs from 'pdfjs-dist';
4
5
 
5
6
  var __defProp = Object.defineProperty;
6
7
  var __defProps = Object.defineProperties;
@@ -124,6 +125,99 @@ var blockStyles = {
124
125
  }
125
126
  }
126
127
  };
128
+ pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
129
+ function PdfImporter({ onScriptImported, children }) {
130
+ const [isProcessing, setIsProcessing] = useState(false);
131
+ const [error, setError] = useState(null);
132
+ const fileInputRef = useRef(null);
133
+ const handleFileChange = async (event) => {
134
+ var _a;
135
+ const file = (_a = event.target.files) == null ? void 0 : _a[0];
136
+ if (!file) {
137
+ return;
138
+ }
139
+ setIsProcessing(true);
140
+ setError(null);
141
+ try {
142
+ const arrayBuffer = await file.arrayBuffer();
143
+ const pdf = await pdfjs.getDocument(arrayBuffer).promise;
144
+ const processPage = async (pageNumber) => {
145
+ const page = await pdf.getPage(pageNumber);
146
+ const content = await page.getTextContent();
147
+ const items = content.items.filter((item) => "str" in item && item.str.trim().length > 0);
148
+ if (items.length === 0) return "";
149
+ const lines = [];
150
+ for (const item of items) {
151
+ let found = false;
152
+ for (const line of lines) {
153
+ if (Math.abs(line.y - item.transform[5]) < 5) {
154
+ line.items.push({ x: item.transform[4], text: item.str });
155
+ found = true;
156
+ break;
157
+ }
158
+ }
159
+ if (!found) {
160
+ lines.push({ y: item.transform[5], items: [{ x: item.transform[4], text: item.str }] });
161
+ }
162
+ }
163
+ lines.sort((a, b) => b.y - a.y);
164
+ return lines.map((line) => {
165
+ line.items.sort((a, b) => a.x - b.x);
166
+ return line.items.map((item) => item.text).join(" ");
167
+ }).join("\n");
168
+ };
169
+ let title = "";
170
+ if (pdf.numPages > 0) {
171
+ title = await processPage(1);
172
+ }
173
+ let scriptContent = "";
174
+ for (let i = 2; i <= pdf.numPages; i++) {
175
+ scriptContent += await processPage(i) + "\n\n";
176
+ }
177
+ onScriptImported(title.trim(), scriptContent);
178
+ } catch (err) {
179
+ console.error("Error processing PDF:", err);
180
+ setError(err instanceof Error ? `Error processing PDF: ${err.message}` : "An unknown error occurred.");
181
+ } finally {
182
+ setIsProcessing(false);
183
+ if (event.target) {
184
+ event.target.value = "";
185
+ }
186
+ }
187
+ };
188
+ const handleClick = () => {
189
+ var _a;
190
+ (_a = fileInputRef.current) == null ? void 0 : _a.click();
191
+ };
192
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
193
+ /* @__PURE__ */ jsx(
194
+ "input",
195
+ {
196
+ ref: fileInputRef,
197
+ type: "file",
198
+ accept: "application/pdf",
199
+ onChange: handleFileChange,
200
+ disabled: isProcessing,
201
+ className: "hidden",
202
+ id: "pdf-importer-input"
203
+ }
204
+ ),
205
+ /* @__PURE__ */ jsx(
206
+ "button",
207
+ {
208
+ onClick: handleClick,
209
+ disabled: isProcessing,
210
+ className: "flex items-center justify-center gap-2 w-auto px-4 h-12 rounded-full bg-zinc-950 text-white shadow-xl shadow-zinc-900/20 border border-white/10 hover:bg-zinc-800 hover:scale-105 active:scale-95 transition-all duration-300",
211
+ "aria-label": "Import Script from PDF",
212
+ children: isProcessing ? /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Processing..." }) : children
213
+ }
214
+ ),
215
+ error && /* @__PURE__ */ jsxs("p", { className: "sr-only", children: [
216
+ "Error: ",
217
+ error
218
+ ] })
219
+ ] });
220
+ }
127
221
  function ScreenplayEditorView({
128
222
  blocks,
129
223
  pages,
@@ -145,9 +239,11 @@ function ScreenplayEditorView({
145
239
  handleKeyDown,
146
240
  handleFocus,
147
241
  handleBlur,
242
+ handleScriptImport,
148
243
  onSave,
149
244
  onSaveAsPdf,
150
- onSyncWithCloud
245
+ onSyncWithCloud,
246
+ handleSceneNumberChange
151
247
  }) {
152
248
  const [isRulesOpen, setIsRulesOpen] = useState(false);
153
249
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -206,7 +302,26 @@ function ScreenplayEditorView({
206
302
  className: `relative rounded-sm transition-all duration-200 outline-none ${focusedBlockId === block.id ? "bg-zinc-100/50" : "bg-transparent"}`,
207
303
  children: block.type === "SCENE_HEADING" ? /* @__PURE__ */ jsxs(Fragment, { children: [
208
304
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-1 bg-transparent", children: [
209
- /* @__PURE__ */ jsx("span", { className: "absolute -left-16 top-2 w-12 text-right text-zinc-400 font-semibold select-none", children: String(sceneNumbers[block.id] || 0) }),
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
+ ),
210
325
  /* @__PURE__ */ jsxs(
211
326
  "select",
212
327
  {
@@ -399,6 +514,10 @@ function ScreenplayEditorView({
399
514
  pageIndex
400
515
  )) }),
401
516
  /* @__PURE__ */ jsxs("div", { className: "fixed bottom-6 right-6 flex flex-col items-end gap-4 z-50", children: [
517
+ /* @__PURE__ */ jsx(PdfImporter, { onScriptImported: handleScriptImport, children: /* @__PURE__ */ jsxs(Fragment, { children: [
518
+ /* @__PURE__ */ jsx(Upload, { className: "w-5 h-5" }),
519
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Import" })
520
+ ] }) }),
402
521
  onSave && /* @__PURE__ */ jsxs(
403
522
  "button",
404
523
  {
@@ -501,6 +620,7 @@ function createNewBlock(type) {
501
620
  if (type === "SCENE_HEADING") {
502
621
  newBlock.sceneType = "INT.";
503
622
  newBlock.timeOfDay = "DAY";
623
+ newBlock.sceneNumber = "";
504
624
  } else if (type === "PARENTHETICAL") {
505
625
  newBlock.text = "()";
506
626
  }
@@ -508,12 +628,12 @@ function createNewBlock(type) {
508
628
  }
509
629
  function addBlockAfter(blocks, currentBlockId) {
510
630
  const currentIndex = blocks.findIndex((b) => b.id === currentBlockId);
511
- if (currentIndex === -1) {
512
- return { newBlocks: blocks, newBlockId: "" };
513
- }
514
- const currentBlock = blocks[currentIndex];
515
- const nextType = getNextBlockType(currentBlock.type);
631
+ if (currentIndex === -1) return { newBlocks: blocks, newBlockId: "" };
632
+ const nextType = getNextBlockType(blocks[currentIndex].type);
516
633
  const newBlock = createNewBlock(nextType);
634
+ if (nextType === "SCENE_HEADING") {
635
+ newBlock.sceneNumber = generateNextSceneNumber(blocks, currentIndex);
636
+ }
517
637
  const newBlocks = [
518
638
  ...blocks.slice(0, currentIndex + 1),
519
639
  newBlock,
@@ -538,10 +658,150 @@ function deleteBlock(blocks, blockIdToDelete) {
538
658
  function updateBlock(blocks, id, key, value) {
539
659
  return blocks.map((b) => b.id === id ? __spreadProps(__spreadValues({}, b), { [key]: value }) : b);
540
660
  }
661
+ var generateNextSceneNumber = (blocks, currentIndex) => {
662
+ const prevScenes = blocks.slice(0, currentIndex + 1).filter((b) => b.type === "SCENE_HEADING");
663
+ if (prevScenes.length === 0) return "1";
664
+ const lastScene = prevScenes[prevScenes.length - 1];
665
+ const lastNum = lastScene.sceneNumber || "1";
666
+ const match = lastNum.match(/^(\d+)([A-Z]*)$/);
667
+ if (!match) return "1";
668
+ const baseNumber = match[1];
669
+ const currentSuffix = match[2];
670
+ const scenesAfter = blocks.slice(currentIndex + 1).filter((b) => b.type === "SCENE_HEADING");
671
+ if (scenesAfter.length > 0) {
672
+ if (currentSuffix === "") {
673
+ return `${baseNumber}A`;
674
+ } else {
675
+ return `${baseNumber}${incrementChar(currentSuffix)}`;
676
+ }
677
+ }
678
+ return String(parseInt(baseNumber) + 1);
679
+ };
680
+ function incrementChar(text) {
681
+ if (text === "") return "A";
682
+ const lastChar = text.slice(-1);
683
+ const remaining = text.slice(0, -1);
684
+ if (lastChar === "Z") {
685
+ return incrementChar(remaining) + "A";
686
+ }
687
+ return remaining + String.fromCharCode(lastChar.charCodeAt(0) + 1);
688
+ }
541
689
  function changeBlockType(blocks, id, newType) {
690
+ const currentIndex = blocks.findIndex((b) => b.id === id);
691
+ if (currentIndex === -1) return blocks;
692
+ const currentBlock = blocks[currentIndex];
542
693
  const newBlock = createNewBlock(newType);
694
+ if (newType === "PARENTHETICAL") {
695
+ const cleanText = currentBlock.text.replace(/[()]/g, "");
696
+ newBlock.text = `(${cleanText})`;
697
+ } else {
698
+ newBlock.text = currentBlock.text;
699
+ }
700
+ if (newType === "SCENE_HEADING") {
701
+ newBlock.sceneNumber = generateNextSceneNumber(blocks, currentIndex);
702
+ newBlock.text = newBlock.text.toUpperCase();
703
+ }
543
704
  return blocks.map((b) => b.id === id ? __spreadProps(__spreadValues({}, newBlock), { id: b.id }) : b);
544
705
  }
706
+ function parseScreenplayText(content) {
707
+ const lines = content.split("\n");
708
+ const blocks = [];
709
+ let lastBlock = null;
710
+ let activeSpeaker = null;
711
+ for (let i = 0; i < lines.length; i++) {
712
+ let originalLine = lines[i];
713
+ let trimmedLine = lines[i].trim().replace(/\s+/g, " ");
714
+ const leadingSpaces = originalLine.search(/\S/);
715
+ if (lines[i].trim() === "") {
716
+ activeSpeaker = null;
717
+ continue;
718
+ }
719
+ const isPageNumber = /^\d+$/.test(trimmedLine) || /^(PAGE|pg\.?)\s?\d+$/i.test(trimmedLine) || /^[\d]+\.$/.test(trimmedLine);
720
+ if (trimmedLine.length === 0 || isPageNumber) continue;
721
+ let currentBlockType = null;
722
+ const isAllUpperCase = trimmedLine === trimmedLine.toUpperCase() && /[A-Z]/.test(trimmedLine);
723
+ const sceneHeadingStartRegex = /^(?:\d+[A-Z]?\.?\s*)?(INT\.?\/EXT\.?|I\/E|INT|EXT|EST\.)\b/i;
724
+ const isTransition = isAllUpperCase && (trimmedLine.endsWith(" TO:") || ["FADE IN:", "FADE OUT.", "CUT TO BLACK."].includes(trimmedLine));
725
+ if (sceneHeadingStartRegex.test(trimmedLine)) {
726
+ currentBlockType = "SCENE_HEADING";
727
+ activeSpeaker = null;
728
+ } else if (isTransition) {
729
+ currentBlockType = "TRANSITION";
730
+ activeSpeaker = null;
731
+ } else if (isAllUpperCase && !trimmedLine.startsWith("(")) {
732
+ let nextLine = "";
733
+ for (let j = i + 1; j < lines.length; j++) {
734
+ const nextTrimmed = lines[j].trim();
735
+ const nextIsPage = /^\d+$/.test(nextTrimmed) || /^(PAGE|pg\.?)\s?\d+$/i.test(nextTrimmed);
736
+ if (nextTrimmed.length > 0 && !nextIsPage) {
737
+ nextLine = nextTrimmed;
738
+ break;
739
+ }
740
+ }
741
+ if (nextLine && (nextLine.startsWith("(") || nextLine !== nextLine.toUpperCase()) || /\s\(.*\)$/.test(trimmedLine)) {
742
+ currentBlockType = "CHARACTER";
743
+ activeSpeaker = trimmedLine;
744
+ } else {
745
+ currentBlockType = "ACTION";
746
+ activeSpeaker = null;
747
+ }
748
+ } else if (trimmedLine.startsWith("(") && trimmedLine.endsWith(")")) {
749
+ currentBlockType = "PARENTHETICAL";
750
+ } else if (activeSpeaker && ((lastBlock == null ? void 0 : lastBlock.type) === "CHARACTER" || (lastBlock == null ? void 0 : lastBlock.type) === "PARENTHETICAL" || (lastBlock == null ? void 0 : lastBlock.type) === "DIALOGUE")) {
751
+ const isLastLineComplete = /[.!?]"?$/.test(lastBlock.text.trim());
752
+ if (leadingSpaces < 15 && (lastBlock == null ? void 0 : lastBlock.type) !== "CHARACTER" && isLastLineComplete) {
753
+ currentBlockType = "ACTION";
754
+ activeSpeaker = null;
755
+ } else {
756
+ currentBlockType = "DIALOGUE";
757
+ }
758
+ } else {
759
+ currentBlockType = "ACTION";
760
+ activeSpeaker = null;
761
+ }
762
+ if (lastBlock && lastBlock.type === currentBlockType && (currentBlockType === "ACTION" || currentBlockType === "DIALOGUE") && !(currentBlockType === "DIALOGUE" && !activeSpeaker)) {
763
+ lastBlock.text += " " + trimmedLine;
764
+ } else {
765
+ const newBlock = createNewBlock(currentBlockType);
766
+ if (currentBlockType === "SCENE_HEADING") {
767
+ let workingLine = trimmedLine;
768
+ const sceneNumMatch = workingLine.match(/^(\d+[A-Z]*)\.?\s+/i);
769
+ if (sceneNumMatch) {
770
+ newBlock.sceneNumber = String(sceneNumMatch[1]).toUpperCase();
771
+ workingLine = workingLine.replace(/^(\d+[A-Z]*)\.?\s+/i, "").trim();
772
+ } else {
773
+ newBlock.sceneNumber = "";
774
+ }
775
+ const typeMatch = workingLine.match(
776
+ /^(INT\.?\/EXT\.?|I\/E|INT|EXT|EST\.)/i
777
+ );
778
+ if (typeMatch) {
779
+ let sType = typeMatch[0].toUpperCase().replace(/[^A-Z/.]/g, "");
780
+ if (!sType.endsWith(".")) sType += ".";
781
+ newBlock.sceneType = sType;
782
+ workingLine = workingLine.substring(typeMatch[0].length).trim();
783
+ }
784
+ const suffixRegex = /[.\-\s]+(DAY|NIGHT|CONTINUOUS|LATER|MORNING|EVENING|DUSK|DAWN|MORN|AFT|SUNSET|SUNRISE)(?:\s+.*)*$/i;
785
+ const suffixMatch = workingLine.match(suffixRegex);
786
+ if (suffixMatch) {
787
+ const rawTime = suffixMatch[1].toUpperCase();
788
+ const nightKeys = ["NIGHT", "EVENING", "DUSK", "SUNSET"];
789
+ newBlock.timeOfDay = nightKeys.includes(rawTime) ? "NIGHT" : "DAY";
790
+ workingLine = workingLine.substring(0, suffixMatch.index).trim();
791
+ } else {
792
+ workingLine = workingLine.replace(/\s+\d+(\s+\d+)*$/, "").trim();
793
+ newBlock.timeOfDay = "DAY";
794
+ }
795
+ newBlock.text = workingLine.replace(/^[-.\s]+/, "").replace(/[-.\s]+$/, "").toUpperCase();
796
+ } else {
797
+ newBlock.text = trimmedLine;
798
+ }
799
+ blocks.push(newBlock);
800
+ lastBlock = newBlock;
801
+ }
802
+ }
803
+ return blocks.length > 0 ? blocks : [createNewBlock("SCENE_HEADING")];
804
+ }
545
805
 
546
806
  // app/hook/use-screenplay-editor.ts
547
807
  var initialBlocks = [
@@ -549,6 +809,7 @@ var initialBlocks = [
549
809
  id: uuid(),
550
810
  type: "SCENE_HEADING",
551
811
  text: "",
812
+ sceneNumber: "1",
552
813
  sceneType: "INT.",
553
814
  timeOfDay: "DAY"
554
815
  }
@@ -627,6 +888,16 @@ function useScreenplayEditor() {
627
888
  () => ["(V.O.)", "(O.S.)", "(O.C.)", "(SUBTITLE)", "(CONT'D)"],
628
889
  []
629
890
  );
891
+ const handleSceneNumberChange = useCallback(
892
+ (id, newNumber) => {
893
+ setBlocks(
894
+ (prevBlocks) => prevBlocks.map(
895
+ (block) => block.id === id ? __spreadProps(__spreadValues({}, block), { sceneNumber: newNumber.toUpperCase() }) : block
896
+ )
897
+ );
898
+ },
899
+ []
900
+ );
630
901
  const locations = useMemo(() => {
631
902
  const locs = blocks.filter((b) => b.type === "SCENE_HEADING" && b.text.trim() !== "").map((b) => b.text.trim().toUpperCase());
632
903
  return [...new Set(locs)];
@@ -644,15 +915,32 @@ function useScreenplayEditor() {
644
915
  }, [blocks]);
645
916
  const sceneNumbers = useMemo(() => {
646
917
  const map = {};
647
- let count = 0;
918
+ let fallbackCount = 0;
648
919
  blocks.forEach((block) => {
649
920
  if (block.type === "SCENE_HEADING") {
650
- count++;
651
- map[block.id] = count;
921
+ if (block.sceneNumber) {
922
+ map[block.id] = block.sceneNumber;
923
+ const base = parseInt(block.sceneNumber);
924
+ if (!isNaN(base)) fallbackCount = Math.max(fallbackCount, base);
925
+ } else {
926
+ fallbackCount++;
927
+ map[block.id] = String(fallbackCount);
928
+ }
652
929
  }
653
930
  });
654
931
  return map;
655
932
  }, [blocks]);
933
+ useCallback(() => {
934
+ let count = 1;
935
+ setBlocks(
936
+ (prev) => prev.map((b) => {
937
+ if (b.type === "SCENE_HEADING") {
938
+ return __spreadProps(__spreadValues({}, b), { sceneNumber: String(count++) });
939
+ }
940
+ return b;
941
+ })
942
+ );
943
+ }, []);
656
944
  useEffect(() => {
657
945
  if (newBlockId && refs.current[newBlockId]) {
658
946
  const block = blocks.find((b) => b.id === newBlockId);
@@ -673,11 +961,13 @@ function useScreenplayEditor() {
673
961
  useEffect(() => {
674
962
  blocks.forEach((block) => {
675
963
  const element = refs.current[block.id];
676
- if (element && element.innerText !== block.text && document.activeElement !== element) {
677
- element.innerText = block.text;
964
+ if (element) {
965
+ if (element.innerText !== block.text && document.activeElement !== element) {
966
+ element.innerText = block.text;
967
+ }
678
968
  }
679
969
  });
680
- }, [blocks, isPageSplitEnabled, pageBreaks]);
970
+ }, [blocks]);
681
971
  useEffect(() => {
682
972
  const handleClickOutside = (e) => {
683
973
  const target = e.target;
@@ -857,17 +1147,15 @@ function useScreenplayEditor() {
857
1147
  const el = refs.current[focusedBlockId];
858
1148
  if (el) {
859
1149
  el.focus();
860
- const newBlock = createNewBlock(newType);
861
- el.innerText = newBlock.text;
862
- if (newType === "PARENTHETICAL") {
863
- setCaretPosition(el, 1);
864
- } else {
865
- setCaretPosition(el, newBlock.text.length);
1150
+ const currentBlock = blocks.find((b) => b.id === focusedBlockId);
1151
+ if (currentBlock) {
1152
+ const pos = newType === "PARENTHETICAL" ? el.innerText.length - 1 : el.innerText.length;
1153
+ setCaretPosition(el, Math.max(0, pos));
866
1154
  }
867
1155
  }
868
- }, 0);
1156
+ }, 10);
869
1157
  },
870
- [focusedBlockId]
1158
+ [focusedBlockId, blocks]
871
1159
  );
872
1160
  const handleSelectCharacterExtension = useCallback(
873
1161
  (extension) => {
@@ -923,8 +1211,6 @@ function useScreenplayEditor() {
923
1211
  const el = refs.current[id];
924
1212
  if (el) {
925
1213
  el.focus();
926
- const newBlock = createNewBlock(newType);
927
- el.innerText = newBlock.text;
928
1214
  if (newType === "PARENTHETICAL") {
929
1215
  setCaretPosition(el, 1);
930
1216
  } else {
@@ -1055,28 +1341,62 @@ function useScreenplayEditor() {
1055
1341
  },
1056
1342
  [blocks, handleBlockTextChange]
1057
1343
  );
1058
- const handleFocus = useCallback((id) => {
1059
- if (blurTimeout.current) {
1060
- clearTimeout(blurTimeout.current);
1061
- }
1062
- setFocusedBlockId(id);
1063
- const block = blocks.find((b) => b.id === id);
1064
- if ((block == null ? void 0 : block.type) === "CHARACTER") {
1065
- const trimmedText = block.text.trim();
1066
- const openParenIndex = trimmedText.lastIndexOf("(");
1067
- const closeParenIndex = trimmedText.lastIndexOf(")");
1068
- if (openParenIndex !== -1 && openParenIndex > closeParenIndex) {
1069
- setShowExtensionSuggestions(true);
1070
- setShowSuggestions(false);
1344
+ const handleScriptImport = useCallback(
1345
+ (title, content) => {
1346
+ const parsedBlocks = parseScreenplayText(content);
1347
+ if (parsedBlocks.length > 0) {
1348
+ let fallbackCount = 1;
1349
+ const finalizedBlocks = parsedBlocks.map((block) => {
1350
+ if (block.type === "SCENE_HEADING") {
1351
+ if (block.sceneNumber && block.sceneNumber.trim().length > 0) {
1352
+ const isPureNumber = /^\d+$/.test(block.sceneNumber);
1353
+ if (isPureNumber) {
1354
+ fallbackCount = parseInt(block.sceneNumber) + 1;
1355
+ }
1356
+ return block;
1357
+ }
1358
+ return __spreadProps(__spreadValues({}, block), { sceneNumber: String(fallbackCount++) });
1359
+ }
1360
+ return block;
1361
+ });
1362
+ setBlocks(finalizedBlocks);
1363
+ setTimeout(() => {
1364
+ var _a;
1365
+ const firstId = parsedBlocks[0].id;
1366
+ if (firstId && refs.current[firstId]) {
1367
+ setFocusedBlockId(firstId);
1368
+ (_a = refs.current[firstId]) == null ? void 0 : _a.focus();
1369
+ }
1370
+ }, 100);
1371
+ }
1372
+ },
1373
+ [refs]
1374
+ );
1375
+ const handleFocus = useCallback(
1376
+ (id) => {
1377
+ if (blurTimeout.current) {
1378
+ clearTimeout(blurTimeout.current);
1379
+ }
1380
+ setFocusedBlockId(id);
1381
+ const block = blocks.find((b) => b.id === id);
1382
+ if ((block == null ? void 0 : block.type) === "CHARACTER") {
1383
+ const trimmedText = block.text.trim();
1384
+ const openParenIndex = trimmedText.lastIndexOf("(");
1385
+ const closeParenIndex = trimmedText.lastIndexOf(")");
1386
+ if (openParenIndex !== -1 && openParenIndex > closeParenIndex) {
1387
+ setShowExtensionSuggestions(true);
1388
+ setShowSuggestions(false);
1389
+ } else {
1390
+ setShowExtensionSuggestions(false);
1391
+ setShowSuggestions(openParenIndex === -1);
1392
+ }
1071
1393
  } else {
1394
+ setShowSuggestions(true);
1072
1395
  setShowExtensionSuggestions(false);
1073
- setShowSuggestions(openParenIndex === -1);
1074
1396
  }
1075
- } else {
1076
- setShowSuggestions(true);
1077
- setShowExtensionSuggestions(false);
1078
- }
1079
- }, [blocks]);
1397
+ },
1398
+ [blocks]
1399
+ );
1080
1400
  const handleBlur = useCallback((id) => {
1081
1401
  if (document.activeElement === refs.current[id]) return;
1082
1402
  blurTimeout.current = setTimeout(() => {
@@ -1103,6 +1423,8 @@ function useScreenplayEditor() {
1103
1423
  handleBlockTypeChange,
1104
1424
  handleSelectCharacterExtension,
1105
1425
  handleKeyDown,
1426
+ handleScriptImport,
1427
+ handleSceneNumberChange,
1106
1428
  handleFocus,
1107
1429
  handleBlur
1108
1430
  };