@vishu1301/script-writing 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4,6 +4,9 @@ var react = require('react');
4
4
  var lucideReact = require('lucide-react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var pdfjs = require('pdfjs-dist');
7
+ var jsPDF = require('jspdf');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
10
 
8
11
  function _interopNamespace(e) {
9
12
  if (e && e.__esModule) return e;
@@ -24,6 +27,7 @@ function _interopNamespace(e) {
24
27
  }
25
28
 
26
29
  var pdfjs__namespace = /*#__PURE__*/_interopNamespace(pdfjs);
30
+ var jsPDF__default = /*#__PURE__*/_interopDefault(jsPDF);
27
31
 
28
32
  var __defProp = Object.defineProperty;
29
33
  var __defProps = Object.defineProperties;
@@ -161,45 +165,107 @@ function PdfImporter({ onScriptImported, children }) {
161
165
  setIsProcessing(true);
162
166
  setError(null);
163
167
  try {
164
- const arrayBuffer = await file.arrayBuffer();
165
- const pdf = await pdfjs__namespace.getDocument(arrayBuffer).promise;
166
- const processPage = async (pageNumber) => {
167
- const page = await pdf.getPage(pageNumber);
168
- const content = await page.getTextContent();
169
- const items = content.items.filter((item) => "str" in item && item.str.trim().length > 0);
170
- if (items.length === 0) return "";
171
- const lines = [];
172
- for (const item of items) {
173
- let found = false;
174
- for (const line of lines) {
175
- if (Math.abs(line.y - item.transform[5]) < 5) {
176
- line.items.push({ x: item.transform[4], text: item.str });
177
- found = true;
168
+ if (file.name.toLowerCase().endsWith(".sbx")) {
169
+ let text = await file.text();
170
+ if (text.includes("&lt;div")) {
171
+ const textarea = document.createElement("textarea");
172
+ textarea.innerHTML = text;
173
+ text = textarea.value;
174
+ }
175
+ const parser = new DOMParser();
176
+ const doc = parser.parseFromString(text, "text/html");
177
+ const divs = Array.from(doc.querySelectorAll("div"));
178
+ const preParsedBlocks = [];
179
+ const typeMap = {
180
+ divtype0: "SCENE_HEADING",
181
+ divtype2: "ACTION",
182
+ divtype3: "CHARACTER",
183
+ divtype4: "PARENTHETICAL",
184
+ divtype5: "DIALOGUE",
185
+ divtype6: "TRANSITION"
186
+ };
187
+ divs.forEach((div) => {
188
+ var _a2;
189
+ let divText = ((_a2 = div.textContent) == null ? void 0 : _a2.trim()) || "";
190
+ if (!divText) return;
191
+ let type = "ACTION";
192
+ for (const className of Array.from(div.classList)) {
193
+ if (typeMap[className]) {
194
+ type = typeMap[className];
178
195
  break;
179
196
  }
180
197
  }
181
- if (!found) {
182
- lines.push({ y: item.transform[5], items: [{ x: item.transform[4], text: item.str }] });
198
+ const block = { type, text: divText };
199
+ if (type === "SCENE_HEADING") {
200
+ const sceneNum = div.getAttribute("data-scene");
201
+ if (sceneNum) block.sceneNumber = sceneNum;
202
+ let parsedText = divText;
203
+ const typeMatch = parsedText.match(/^(INT\/EXT|INT|EXT)\.?\s+/i);
204
+ if (typeMatch) {
205
+ let sType = typeMatch[1].toUpperCase();
206
+ if (!sType.endsWith(".")) sType += ".";
207
+ block.sceneType = sType;
208
+ parsedText = parsedText.substring(typeMatch[0].length).trim();
209
+ }
210
+ const timeMatch = parsedText.match(/\s+-\s+([^-]+)$/);
211
+ if (timeMatch) {
212
+ block.timeOfDay = timeMatch[1].trim().toUpperCase();
213
+ parsedText = parsedText.substring(0, timeMatch.index).trim();
214
+ }
215
+ block.text = parsedText;
216
+ }
217
+ preParsedBlocks.push(block);
218
+ });
219
+ const title = file.name.replace(/\.sbx$/i, "");
220
+ onScriptImported(title.trim(), "", preParsedBlocks);
221
+ } else {
222
+ const arrayBuffer = await file.arrayBuffer();
223
+ const pdf = await pdfjs__namespace.getDocument(arrayBuffer).promise;
224
+ const processPage = async (pageNumber) => {
225
+ const page = await pdf.getPage(pageNumber);
226
+ const content = await page.getTextContent();
227
+ const items = content.items.filter(
228
+ (item) => "str" in item && item.str.trim().length > 0
229
+ );
230
+ if (items.length === 0) return "";
231
+ const lines = [];
232
+ for (const item of items) {
233
+ let found = false;
234
+ for (const line of lines) {
235
+ if (Math.abs(line.y - item.transform[5]) < 5) {
236
+ line.items.push({ x: item.transform[4], text: item.str });
237
+ found = true;
238
+ break;
239
+ }
240
+ }
241
+ if (!found) {
242
+ lines.push({
243
+ y: item.transform[5],
244
+ items: [{ x: item.transform[4], text: item.str }]
245
+ });
246
+ }
183
247
  }
248
+ lines.sort((a, b) => b.y - a.y);
249
+ return lines.map((line) => {
250
+ line.items.sort((a, b) => a.x - b.x);
251
+ return line.items.map((item) => item.text).join(" ");
252
+ }).join("\n");
253
+ };
254
+ let title = "";
255
+ if (pdf.numPages > 0) {
256
+ title = await processPage(1);
184
257
  }
185
- lines.sort((a, b) => b.y - a.y);
186
- return lines.map((line) => {
187
- line.items.sort((a, b) => a.x - b.x);
188
- return line.items.map((item) => item.text).join(" ");
189
- }).join("\n");
190
- };
191
- let title = "";
192
- if (pdf.numPages > 0) {
193
- title = await processPage(1);
194
- }
195
- let scriptContent = "";
196
- for (let i = 2; i <= pdf.numPages; i++) {
197
- scriptContent += await processPage(i) + "\n\n";
258
+ let scriptContent = "";
259
+ for (let i = 2; i <= pdf.numPages; i++) {
260
+ scriptContent += await processPage(i) + "\n\n";
261
+ }
262
+ onScriptImported(title.trim(), scriptContent);
198
263
  }
199
- onScriptImported(title.trim(), scriptContent);
200
264
  } catch (err) {
201
265
  console.error("Error processing PDF:", err);
202
- setError(err instanceof Error ? `Error processing PDF: ${err.message}` : "An unknown error occurred.");
266
+ setError(
267
+ err instanceof Error ? `Error processing PDF: ${err.message}` : "An unknown error occurred."
268
+ );
203
269
  } finally {
204
270
  setIsProcessing(false);
205
271
  if (event.target) {
@@ -217,7 +283,7 @@ function PdfImporter({ onScriptImported, children }) {
217
283
  {
218
284
  ref: fileInputRef,
219
285
  type: "file",
220
- accept: "application/pdf",
286
+ accept: ".pdf,.sbx,application/pdf",
221
287
  onChange: handleFileChange,
222
288
  disabled: isProcessing,
223
289
  className: "hidden",
@@ -230,7 +296,7 @@ function PdfImporter({ onScriptImported, children }) {
230
296
  onClick: handleClick,
231
297
  disabled: isProcessing,
232
298
  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",
233
- "aria-label": "Import Script from PDF",
299
+ "aria-label": "Import Script",
234
300
  children: isProcessing ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: "Processing..." }) : children
235
301
  }
236
302
  ),
@@ -260,6 +326,7 @@ function ScreenplayEditorView({
260
326
  handleScriptImport,
261
327
  onSave,
262
328
  onSaveAsPdf,
329
+ onSaveAsSbx,
263
330
  onSyncWithCloud,
264
331
  handleSceneNumberChange
265
332
  }) {
@@ -553,6 +620,18 @@ function ScreenplayEditorView({
553
620
  ]
554
621
  }
555
622
  ),
623
+ onSaveAsSbx && /* @__PURE__ */ jsxRuntime.jsxs(
624
+ "button",
625
+ {
626
+ onClick: onSaveAsSbx,
627
+ 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",
628
+ "aria-label": "Save Script as SBX",
629
+ children: [
630
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCcw, { className: "w-5 h-5" }),
631
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: "Save" })
632
+ ]
633
+ }
634
+ ),
556
635
  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: [
557
636
  /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-bold text-zinc-800 mb-3 text-sm", children: "Settings & Shortcuts" }),
558
637
  /* @__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: [
@@ -1215,8 +1294,20 @@ function useScreenplayEditor() {
1215
1294
  [blocks, handleBlockTextChange]
1216
1295
  );
1217
1296
  const handleScriptImport = react.useCallback(
1218
- (title, content) => {
1219
- const parsedBlocks = parseScreenplayText(content);
1297
+ (title, content, preParsedBlocks) => {
1298
+ let parsedBlocks = [];
1299
+ if (preParsedBlocks && preParsedBlocks.length > 0) {
1300
+ parsedBlocks = preParsedBlocks.map((b) => ({
1301
+ id: uuid(),
1302
+ type: b.type || "ACTION",
1303
+ text: b.text || "",
1304
+ sceneNumber: b.sceneNumber,
1305
+ sceneType: b.sceneType,
1306
+ timeOfDay: b.timeOfDay
1307
+ }));
1308
+ } else {
1309
+ parsedBlocks = parseScreenplayText(content);
1310
+ }
1220
1311
  if (parsedBlocks.length > 0) {
1221
1312
  let fallbackCount = 1;
1222
1313
  const finalizedBlocks = parsedBlocks.map((block) => {
@@ -1299,10 +1390,138 @@ function useScreenplayEditor() {
1299
1390
  handleBlur
1300
1391
  };
1301
1392
  }
1393
+ var handleSaveAsPdf = (blocks, sceneNumbers) => {
1394
+ if (document.activeElement instanceof HTMLElement) {
1395
+ document.activeElement.blur();
1396
+ }
1397
+ const doc = new jsPDF__default.default({
1398
+ orientation: "portrait",
1399
+ unit: "mm",
1400
+ format: "letter"
1401
+ });
1402
+ const FONT_SIZE = 12;
1403
+ const LINE_HEIGHT = 4.233;
1404
+ const PAGE_WIDTH = doc.internal.pageSize.getWidth();
1405
+ const PAGE_HEIGHT = doc.internal.pageSize.getHeight();
1406
+ const MARGIN_LEFT = 38.1;
1407
+ const MARGIN_RIGHT = 25.4;
1408
+ const MARGIN_TOP = 25.4;
1409
+ const MARGIN_BOTTOM = 25.4;
1410
+ const blockDimensions = {
1411
+ SCENE_HEADING: { indent: 0, width: 152.4, upper: true, bold: true },
1412
+ ACTION: { indent: 0, width: 152.4, upper: false, bold: false },
1413
+ CHARACTER: { indent: 50.8, width: 101.6, upper: true, bold: false },
1414
+ PARENTHETICAL: { indent: 38.1, width: 50.8, upper: false, bold: false },
1415
+ DIALOGUE: { indent: 25.4, width: 88.9, upper: false, bold: false },
1416
+ TRANSITION: {
1417
+ indent: 0,
1418
+ width: 152.4,
1419
+ upper: true,
1420
+ bold: false,
1421
+ align: "right"
1422
+ },
1423
+ GENERAL: { indent: 0, width: 152.4, upper: false, bold: false }
1424
+ };
1425
+ let y = MARGIN_TOP;
1426
+ let pageNumber = 1;
1427
+ const drawPageNumber = (num) => {
1428
+ if (num > 1) {
1429
+ doc.setFont("Courier", "normal");
1430
+ doc.setFontSize(12);
1431
+ doc.text(`${num}.`, PAGE_WIDTH - MARGIN_RIGHT, 12.7, {
1432
+ align: "right"
1433
+ });
1434
+ }
1435
+ };
1436
+ blocks.forEach((block, index) => {
1437
+ const config = blockDimensions[block.type] || blockDimensions.GENERAL;
1438
+ let text = block.text || "";
1439
+ if (config.upper) text = text.toUpperCase();
1440
+ if (block.type === "SCENE_HEADING") {
1441
+ text = `${block.sceneType || "INT."} ${text} - ${block.timeOfDay || "DAY"}`.toUpperCase();
1442
+ }
1443
+ doc.setFont("Courier", config.bold ? "bold" : "normal");
1444
+ doc.setFontSize(FONT_SIZE);
1445
+ const lines = doc.splitTextToSize(text, config.width);
1446
+ const blockHeight = lines.length * LINE_HEIGHT;
1447
+ let safetyBuffer = 0;
1448
+ if (block.type === "CHARACTER") safetyBuffer = LINE_HEIGHT * 3;
1449
+ if (y + blockHeight + safetyBuffer > PAGE_HEIGHT - MARGIN_BOTTOM) {
1450
+ doc.addPage();
1451
+ pageNumber++;
1452
+ drawPageNumber(pageNumber);
1453
+ y = MARGIN_TOP;
1454
+ doc.setFont("Courier", config.bold ? "bold" : "normal");
1455
+ doc.setFontSize(FONT_SIZE);
1456
+ }
1457
+ if (y > MARGIN_TOP) {
1458
+ if (block.type === "SCENE_HEADING" || block.type === "ACTION" || block.type === "CHARACTER") {
1459
+ y += LINE_HEIGHT;
1460
+ }
1461
+ }
1462
+ const xPos = MARGIN_LEFT + config.indent;
1463
+ if (config.align === "right") {
1464
+ doc.text(lines, PAGE_WIDTH - MARGIN_RIGHT, y, { align: "right" });
1465
+ } else {
1466
+ doc.text(lines, xPos, y);
1467
+ }
1468
+ if (block.type === "SCENE_HEADING" && (sceneNumbers == null ? void 0 : sceneNumbers[block.id])) {
1469
+ const sNum = String(sceneNumbers[block.id]);
1470
+ doc.setFont("Courier", "normal");
1471
+ doc.setFontSize(FONT_SIZE);
1472
+ doc.text(sNum, MARGIN_LEFT - 12, y);
1473
+ doc.text(sNum, PAGE_WIDTH - MARGIN_RIGHT + 5, y);
1474
+ doc.setFont("Courier", config.bold ? "bold" : "normal");
1475
+ }
1476
+ y += blockHeight;
1477
+ });
1478
+ doc.save("screenplay_export.pdf");
1479
+ };
1480
+ var handleSaveAsSbx = (blocks, sceneNumbers, onSaveAsSbx) => {
1481
+ const typeToDivClass = {
1482
+ SCENE_HEADING: "divtype0",
1483
+ ACTION: "divtype2",
1484
+ CHARACTER: "divtype3",
1485
+ PARENTHETICAL: "divtype4",
1486
+ DIALOGUE: "divtype5",
1487
+ TRANSITION: "divtype6",
1488
+ GENERAL: "divtype2"
1489
+ };
1490
+ const sbxData = blocks.map((block) => {
1491
+ const divClass = typeToDivClass[block.type] || "divtype2";
1492
+ let text = block.text || "";
1493
+ let extraAttributes = "";
1494
+ if (block.type === "SCENE_HEADING") {
1495
+ text = `${block.sceneType || "INT."} ${text} - ${block.timeOfDay || "DAY"}`.toUpperCase();
1496
+ const sceneNum = sceneNumbers == null ? void 0 : sceneNumbers[block.id];
1497
+ if (sceneNum) {
1498
+ extraAttributes = ` data-scene="${sceneNum}"`;
1499
+ }
1500
+ } else if (block.type === "CHARACTER" || block.type === "TRANSITION") {
1501
+ text = text.toUpperCase();
1502
+ }
1503
+ return `<div class="${divClass}" id="par${block.id}"${extraAttributes}>${text}</div>`;
1504
+ }).join("");
1505
+ const blob = new Blob([sbxData], { type: "text/plain" });
1506
+ const url = URL.createObjectURL(blob);
1507
+ const a = document.createElement("a");
1508
+ a.href = url;
1509
+ a.download = "screenplay.sbx";
1510
+ document.body.appendChild(a);
1511
+ a.click();
1512
+ document.body.removeChild(a);
1513
+ URL.revokeObjectURL(url);
1514
+ if (onSaveAsSbx) {
1515
+ const file = new File([blob], "screenplay.sbx", { type: "text/plain" });
1516
+ onSaveAsSbx(file);
1517
+ }
1518
+ };
1302
1519
 
1303
1520
  exports.ScreenplayEditorView = ScreenplayEditorView;
1304
1521
  exports.blockStyles = blockStyles;
1305
1522
  exports.blockTypes = blockTypes;
1523
+ exports.handleSaveAsPdf = handleSaveAsPdf;
1524
+ exports.handleSaveAsSbx = handleSaveAsSbx;
1306
1525
  exports.icons = icons;
1307
1526
  exports.timeOfDayOptions = timeOfDayOptions;
1308
1527
  exports.useScreenplayEditor = useScreenplayEditor;