@vishu1301/script-writing 0.4.1 → 0.4.3

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;
@@ -73,7 +74,6 @@ var blockStyles = {
73
74
  textAlign: "left",
74
75
  marginLeft: "2.0in",
75
76
  maxWidth: "4.0in",
76
- fontWeight: 700,
77
77
  letterSpacing: "0.1em",
78
78
  outline: "none",
79
79
  whiteSpace: "pre-wrap",
@@ -125,6 +125,99 @@ var blockStyles = {
125
125
  }
126
126
  }
127
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
+ }
128
221
  function ScreenplayEditorView({
129
222
  blocks,
130
223
  pages,
@@ -146,9 +239,11 @@ function ScreenplayEditorView({
146
239
  handleKeyDown,
147
240
  handleFocus,
148
241
  handleBlur,
242
+ handleScriptImport,
149
243
  onSave,
150
244
  onSaveAsPdf,
151
- onSyncWithCloud
245
+ onSyncWithCloud,
246
+ handleSceneNumberChange
152
247
  }) {
153
248
  const [isRulesOpen, setIsRulesOpen] = useState(false);
154
249
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -207,7 +302,26 @@ function ScreenplayEditorView({
207
302
  className: `relative rounded-sm transition-all duration-200 outline-none ${focusedBlockId === block.id ? "bg-zinc-100/50" : "bg-transparent"}`,
208
303
  children: block.type === "SCENE_HEADING" ? /* @__PURE__ */ jsxs(Fragment, { children: [
209
304
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-1 bg-transparent", children: [
210
- /* @__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
+ ),
211
325
  /* @__PURE__ */ jsxs(
212
326
  "select",
213
327
  {
@@ -400,6 +514,10 @@ function ScreenplayEditorView({
400
514
  pageIndex
401
515
  )) }),
402
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
+ ] }) }),
403
521
  onSave && /* @__PURE__ */ jsxs(
404
522
  "button",
405
523
  {
@@ -502,6 +620,7 @@ function createNewBlock(type) {
502
620
  if (type === "SCENE_HEADING") {
503
621
  newBlock.sceneType = "INT.";
504
622
  newBlock.timeOfDay = "DAY";
623
+ newBlock.sceneNumber = "";
505
624
  } else if (type === "PARENTHETICAL") {
506
625
  newBlock.text = "()";
507
626
  }
@@ -509,12 +628,12 @@ function createNewBlock(type) {
509
628
  }
510
629
  function addBlockAfter(blocks, currentBlockId) {
511
630
  const currentIndex = blocks.findIndex((b) => b.id === currentBlockId);
512
- if (currentIndex === -1) {
513
- return { newBlocks: blocks, newBlockId: "" };
514
- }
515
- const currentBlock = blocks[currentIndex];
516
- const nextType = getNextBlockType(currentBlock.type);
631
+ if (currentIndex === -1) return { newBlocks: blocks, newBlockId: "" };
632
+ const nextType = getNextBlockType(blocks[currentIndex].type);
517
633
  const newBlock = createNewBlock(nextType);
634
+ if (nextType === "SCENE_HEADING") {
635
+ newBlock.sceneNumber = generateNextSceneNumber(blocks, currentIndex);
636
+ }
518
637
  const newBlocks = [
519
638
  ...blocks.slice(0, currentIndex + 1),
520
639
  newBlock,
@@ -539,10 +658,142 @@ function deleteBlock(blocks, blockIdToDelete) {
539
658
  function updateBlock(blocks, id, key, value) {
540
659
  return blocks.map((b) => b.id === id ? __spreadProps(__spreadValues({}, b), { [key]: value }) : b);
541
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
+ }
542
689
  function changeBlockType(blocks, id, newType) {
690
+ const currentIndex = blocks.findIndex((b) => b.id === id);
691
+ if (currentIndex === -1) return blocks;
543
692
  const newBlock = createNewBlock(newType);
693
+ if (newType === "SCENE_HEADING") {
694
+ newBlock.sceneNumber = generateNextSceneNumber(blocks, currentIndex);
695
+ }
544
696
  return blocks.map((b) => b.id === id ? __spreadProps(__spreadValues({}, newBlock), { id: b.id }) : b);
545
697
  }
698
+ function parseScreenplayText(content) {
699
+ const lines = content.split("\n");
700
+ const blocks = [];
701
+ let lastBlock = null;
702
+ let activeSpeaker = null;
703
+ for (let i = 0; i < lines.length; i++) {
704
+ let originalLine = lines[i];
705
+ let trimmedLine = lines[i].trim().replace(/\s+/g, " ");
706
+ const leadingSpaces = originalLine.search(/\S/);
707
+ if (lines[i].trim() === "") {
708
+ activeSpeaker = null;
709
+ continue;
710
+ }
711
+ const isPageNumber = /^\d+$/.test(trimmedLine) || /^(PAGE|pg\.?)\s?\d+$/i.test(trimmedLine) || /^[\d]+\.$/.test(trimmedLine);
712
+ if (trimmedLine.length === 0 || isPageNumber) continue;
713
+ let currentBlockType = null;
714
+ const isAllUpperCase = trimmedLine === trimmedLine.toUpperCase() && /[A-Z]/.test(trimmedLine);
715
+ const sceneHeadingStartRegex = /^(?:\d+[A-Z]?\.?\s*)?(INT\.?\/EXT\.?|I\/E|INT|EXT|EST\.)\b/i;
716
+ const isTransition = isAllUpperCase && (trimmedLine.endsWith(" TO:") || ["FADE IN:", "FADE OUT.", "CUT TO BLACK."].includes(trimmedLine));
717
+ if (sceneHeadingStartRegex.test(trimmedLine)) {
718
+ currentBlockType = "SCENE_HEADING";
719
+ activeSpeaker = null;
720
+ } else if (isTransition) {
721
+ currentBlockType = "TRANSITION";
722
+ activeSpeaker = null;
723
+ } else if (isAllUpperCase && !trimmedLine.startsWith("(")) {
724
+ let nextLine = "";
725
+ for (let j = i + 1; j < lines.length; j++) {
726
+ const nextTrimmed = lines[j].trim();
727
+ const nextIsPage = /^\d+$/.test(nextTrimmed) || /^(PAGE|pg\.?)\s?\d+$/i.test(nextTrimmed);
728
+ if (nextTrimmed.length > 0 && !nextIsPage) {
729
+ nextLine = nextTrimmed;
730
+ break;
731
+ }
732
+ }
733
+ if (nextLine && (nextLine.startsWith("(") || nextLine !== nextLine.toUpperCase()) || /\s\(.*\)$/.test(trimmedLine)) {
734
+ currentBlockType = "CHARACTER";
735
+ activeSpeaker = trimmedLine;
736
+ } else {
737
+ currentBlockType = "ACTION";
738
+ activeSpeaker = null;
739
+ }
740
+ } else if (trimmedLine.startsWith("(") && trimmedLine.endsWith(")")) {
741
+ currentBlockType = "PARENTHETICAL";
742
+ } else if (activeSpeaker && ((lastBlock == null ? void 0 : lastBlock.type) === "CHARACTER" || (lastBlock == null ? void 0 : lastBlock.type) === "PARENTHETICAL" || (lastBlock == null ? void 0 : lastBlock.type) === "DIALOGUE")) {
743
+ const isLastLineComplete = /[.!?]"?$/.test(lastBlock.text.trim());
744
+ if (leadingSpaces < 15 && (lastBlock == null ? void 0 : lastBlock.type) !== "CHARACTER" && isLastLineComplete) {
745
+ currentBlockType = "ACTION";
746
+ activeSpeaker = null;
747
+ } else {
748
+ currentBlockType = "DIALOGUE";
749
+ }
750
+ } else {
751
+ currentBlockType = "ACTION";
752
+ activeSpeaker = null;
753
+ }
754
+ if (lastBlock && lastBlock.type === currentBlockType && (currentBlockType === "ACTION" || currentBlockType === "DIALOGUE") && !(currentBlockType === "DIALOGUE" && !activeSpeaker)) {
755
+ lastBlock.text += " " + trimmedLine;
756
+ } else {
757
+ const newBlock = createNewBlock(currentBlockType);
758
+ if (currentBlockType === "SCENE_HEADING") {
759
+ let workingLine = trimmedLine;
760
+ const sceneNumMatch = workingLine.match(/^(\d+[A-Z]*)\.?\s+/i);
761
+ if (sceneNumMatch) {
762
+ newBlock.sceneNumber = String(sceneNumMatch[1]).toUpperCase();
763
+ workingLine = workingLine.replace(/^(\d+[A-Z]*)\.?\s+/i, "").trim();
764
+ } else {
765
+ newBlock.sceneNumber = "";
766
+ }
767
+ const typeMatch = workingLine.match(
768
+ /^(INT\.?\/EXT\.?|I\/E|INT|EXT|EST\.)/i
769
+ );
770
+ if (typeMatch) {
771
+ let sType = typeMatch[0].toUpperCase().replace(/[^A-Z/.]/g, "");
772
+ if (!sType.endsWith(".")) sType += ".";
773
+ newBlock.sceneType = sType;
774
+ workingLine = workingLine.substring(typeMatch[0].length).trim();
775
+ }
776
+ const suffixRegex = /[.\-\s]+(DAY|NIGHT|CONTINUOUS|LATER|MORNING|EVENING|DUSK|DAWN|MORN|AFT|SUNSET|SUNRISE)(?:\s+.*)*$/i;
777
+ const suffixMatch = workingLine.match(suffixRegex);
778
+ if (suffixMatch) {
779
+ const rawTime = suffixMatch[1].toUpperCase();
780
+ const nightKeys = ["NIGHT", "EVENING", "DUSK", "SUNSET"];
781
+ newBlock.timeOfDay = nightKeys.includes(rawTime) ? "NIGHT" : "DAY";
782
+ workingLine = workingLine.substring(0, suffixMatch.index).trim();
783
+ } else {
784
+ workingLine = workingLine.replace(/\s+\d+(\s+\d+)*$/, "").trim();
785
+ newBlock.timeOfDay = "DAY";
786
+ }
787
+ newBlock.text = workingLine.replace(/^[-.\s]+/, "").replace(/[-.\s]+$/, "").toUpperCase();
788
+ } else {
789
+ newBlock.text = trimmedLine;
790
+ }
791
+ blocks.push(newBlock);
792
+ lastBlock = newBlock;
793
+ }
794
+ }
795
+ return blocks.length > 0 ? blocks : [createNewBlock("SCENE_HEADING")];
796
+ }
546
797
 
547
798
  // app/hook/use-screenplay-editor.ts
548
799
  var initialBlocks = [
@@ -550,6 +801,7 @@ var initialBlocks = [
550
801
  id: uuid(),
551
802
  type: "SCENE_HEADING",
552
803
  text: "",
804
+ sceneNumber: "1",
553
805
  sceneType: "INT.",
554
806
  timeOfDay: "DAY"
555
807
  }
@@ -628,6 +880,16 @@ function useScreenplayEditor() {
628
880
  () => ["(V.O.)", "(O.S.)", "(O.C.)", "(SUBTITLE)", "(CONT'D)"],
629
881
  []
630
882
  );
883
+ const handleSceneNumberChange = useCallback(
884
+ (id, newNumber) => {
885
+ setBlocks(
886
+ (prevBlocks) => prevBlocks.map(
887
+ (block) => block.id === id ? __spreadProps(__spreadValues({}, block), { sceneNumber: newNumber.toUpperCase() }) : block
888
+ )
889
+ );
890
+ },
891
+ []
892
+ );
631
893
  const locations = useMemo(() => {
632
894
  const locs = blocks.filter((b) => b.type === "SCENE_HEADING" && b.text.trim() !== "").map((b) => b.text.trim().toUpperCase());
633
895
  return [...new Set(locs)];
@@ -645,15 +907,32 @@ function useScreenplayEditor() {
645
907
  }, [blocks]);
646
908
  const sceneNumbers = useMemo(() => {
647
909
  const map = {};
648
- let count = 0;
910
+ let fallbackCount = 0;
649
911
  blocks.forEach((block) => {
650
912
  if (block.type === "SCENE_HEADING") {
651
- count++;
652
- map[block.id] = count;
913
+ if (block.sceneNumber) {
914
+ map[block.id] = block.sceneNumber;
915
+ const base = parseInt(block.sceneNumber);
916
+ if (!isNaN(base)) fallbackCount = Math.max(fallbackCount, base);
917
+ } else {
918
+ fallbackCount++;
919
+ map[block.id] = String(fallbackCount);
920
+ }
653
921
  }
654
922
  });
655
923
  return map;
656
924
  }, [blocks]);
925
+ useCallback(() => {
926
+ let count = 1;
927
+ setBlocks(
928
+ (prev) => prev.map((b) => {
929
+ if (b.type === "SCENE_HEADING") {
930
+ return __spreadProps(__spreadValues({}, b), { sceneNumber: String(count++) });
931
+ }
932
+ return b;
933
+ })
934
+ );
935
+ }, []);
657
936
  useEffect(() => {
658
937
  if (newBlockId && refs.current[newBlockId]) {
659
938
  const block = blocks.find((b) => b.id === newBlockId);
@@ -1056,28 +1335,62 @@ function useScreenplayEditor() {
1056
1335
  },
1057
1336
  [blocks, handleBlockTextChange]
1058
1337
  );
1059
- const handleFocus = useCallback((id) => {
1060
- if (blurTimeout.current) {
1061
- clearTimeout(blurTimeout.current);
1062
- }
1063
- setFocusedBlockId(id);
1064
- const block = blocks.find((b) => b.id === id);
1065
- if ((block == null ? void 0 : block.type) === "CHARACTER") {
1066
- const trimmedText = block.text.trim();
1067
- const openParenIndex = trimmedText.lastIndexOf("(");
1068
- const closeParenIndex = trimmedText.lastIndexOf(")");
1069
- if (openParenIndex !== -1 && openParenIndex > closeParenIndex) {
1070
- setShowExtensionSuggestions(true);
1071
- setShowSuggestions(false);
1338
+ const handleScriptImport = useCallback(
1339
+ (title, content) => {
1340
+ const parsedBlocks = parseScreenplayText(content);
1341
+ if (parsedBlocks.length > 0) {
1342
+ let fallbackCount = 1;
1343
+ const finalizedBlocks = parsedBlocks.map((block) => {
1344
+ if (block.type === "SCENE_HEADING") {
1345
+ if (block.sceneNumber && block.sceneNumber.trim().length > 0) {
1346
+ const isPureNumber = /^\d+$/.test(block.sceneNumber);
1347
+ if (isPureNumber) {
1348
+ fallbackCount = parseInt(block.sceneNumber) + 1;
1349
+ }
1350
+ return block;
1351
+ }
1352
+ return __spreadProps(__spreadValues({}, block), { sceneNumber: String(fallbackCount++) });
1353
+ }
1354
+ return block;
1355
+ });
1356
+ setBlocks(finalizedBlocks);
1357
+ setTimeout(() => {
1358
+ var _a;
1359
+ const firstId = parsedBlocks[0].id;
1360
+ if (firstId && refs.current[firstId]) {
1361
+ setFocusedBlockId(firstId);
1362
+ (_a = refs.current[firstId]) == null ? void 0 : _a.focus();
1363
+ }
1364
+ }, 100);
1365
+ }
1366
+ },
1367
+ [refs]
1368
+ );
1369
+ const handleFocus = useCallback(
1370
+ (id) => {
1371
+ if (blurTimeout.current) {
1372
+ clearTimeout(blurTimeout.current);
1373
+ }
1374
+ setFocusedBlockId(id);
1375
+ const block = blocks.find((b) => b.id === id);
1376
+ if ((block == null ? void 0 : block.type) === "CHARACTER") {
1377
+ const trimmedText = block.text.trim();
1378
+ const openParenIndex = trimmedText.lastIndexOf("(");
1379
+ const closeParenIndex = trimmedText.lastIndexOf(")");
1380
+ if (openParenIndex !== -1 && openParenIndex > closeParenIndex) {
1381
+ setShowExtensionSuggestions(true);
1382
+ setShowSuggestions(false);
1383
+ } else {
1384
+ setShowExtensionSuggestions(false);
1385
+ setShowSuggestions(openParenIndex === -1);
1386
+ }
1072
1387
  } else {
1388
+ setShowSuggestions(true);
1073
1389
  setShowExtensionSuggestions(false);
1074
- setShowSuggestions(openParenIndex === -1);
1075
1390
  }
1076
- } else {
1077
- setShowSuggestions(true);
1078
- setShowExtensionSuggestions(false);
1079
- }
1080
- }, [blocks]);
1391
+ },
1392
+ [blocks]
1393
+ );
1081
1394
  const handleBlur = useCallback((id) => {
1082
1395
  if (document.activeElement === refs.current[id]) return;
1083
1396
  blurTimeout.current = setTimeout(() => {
@@ -1104,6 +1417,8 @@ function useScreenplayEditor() {
1104
1417
  handleBlockTypeChange,
1105
1418
  handleSelectCharacterExtension,
1106
1419
  handleKeyDown,
1420
+ handleScriptImport,
1421
+ handleSceneNumberChange,
1107
1422
  handleFocus,
1108
1423
  handleBlur
1109
1424
  };