@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 +254 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +251 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
168
|
+
if (file.name.toLowerCase().endsWith(".sbx")) {
|
|
169
|
+
let text = await file.text();
|
|
170
|
+
if (text.includes("<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
|
-
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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;
|