Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/actions/apply-version/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ runs:
src-tauri/tauri.appstore.conf.json > src-tauri/tauri.appstore.conf.json.tmp
mv src-tauri/tauri.appstore.conf.json.tmp src-tauri/tauri.appstore.conf.json

jq --arg id "$WINDOWS_IDENTIFIER" '.identifier = $id' \
jq --arg id "$WINDOWS_IDENTIFIER" --arg v "$INPUT_VERSION" \
'.identifier = $id | .version = $v' \
src-tauri/tauri.windows.conf.json > src-tauri/tauri.windows.conf.json.tmp
mv src-tauri/tauri.windows.conf.json.tmp src-tauri/tauri.windows.conf.json

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:

- name: Lint
run: npm run lint
continue-on-error: true

- name: Typecheck
run: npx tsc --noEmit
Expand Down
33 changes: 22 additions & 11 deletions src/lib/adapters/fdx/finaldraft-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ import { BaseExportOptions, ProjectAdapter } from "../screenplay-adapter";
import { XMLBuilder, XMLParser } from "@node_modules/fast-xml-parser/src/fxp";
import { getNodeFlattenContent } from "@src/lib/screenplay/screenplay";
import { ProjectData, ProjectState } from "@src/lib/project/project-state";
import type { JSONContent } from "@tiptap/core";

interface FDXStyledText {
"#text": string;
"@_Style"?: string;
}

interface FDXParagraphNode {
Text: FDXStyledText[];
"@_Type"?: string;
SceneProperties?: { "@_Length": string; "@_Page": string; "@_Title": string };
}

const options = { attributeNamePrefix: "@_", textNodeName: "#text", ignoreAttributes: false, format: true };
const builder = new XMLBuilder(options);
Expand Down Expand Up @@ -37,8 +49,8 @@ export class FinalDraftAdapter extends ProjectAdapter<BaseExportOptions> {
extension = "fdx";

convertTo(project: ProjectState, options: BaseExportOptions): Promise<Blob> {
let paragraphNodes: any = [];
let nodes = project.screenplay();
const paragraphNodes: FDXParagraphNode[] = [];
const nodes = project.screenplay();
const characters = options.characters;

for (let i = 0; i < nodes.length; i++) {
Expand All @@ -47,7 +59,6 @@ export class FinalDraftAdapter extends ProjectAdapter<BaseExportOptions> {
const content = nodes[i].content!;
const flatText: string = getNodeFlattenContent(content);
const type: string = nodes[i].attrs?.class;
const nextType: string = i >= nodes.length - 1 ? "action" : nodes[i + 1].attrs?.class;

// Don't export unselected characters
if (type === "character" && characters && !characters.includes(flatText)) {
Expand All @@ -64,22 +75,22 @@ export class FinalDraftAdapter extends ProjectAdapter<BaseExportOptions> {
continue;
}

let textNodes: any[] = [];
const textNodes: FDXStyledText[] = [];
for (let j = 0; j < content.length; j++) {
// <Text Style="style">
const childNode = content[j];
const textFragment: string = "text" in childNode ? childNode.text! : "";
const styledNode: any = { "#text": textFragment };
const styledNode: FDXStyledText = { "#text": textFragment };

const styles: string[] = (content[j].marks ?? []).map((mark: any) => FDX_STYLE_TABLE[mark.type]);
const styles: string[] = (content[j].marks ?? []).map((mark) => FDX_STYLE_TABLE[mark.type]);
const fdxStyle: string = styles.join("+");
if (fdxStyle) styledNode["@_Style"] = fdxStyle;

textNodes.push(styledNode);
}

// <Paragraph Type="type">
const paragraphNode: any = { Text: textNodes };
const paragraphNode: FDXParagraphNode = { Text: textNodes };
paragraphNode["@_Type"] = FDX_ELEMENT_TABLE[type];

if (type === "scene") {
Expand Down Expand Up @@ -123,7 +134,7 @@ export class FinalDraftAdapter extends ProjectAdapter<BaseExportOptions> {
// Ensure paragraphs is always an array
const paragraphList = Array.isArray(paragraphs) ? paragraphs : [paragraphs];

const screenplay: any[] = [];
const screenplay: JSONContent[] = [];

for (const paragraph of paragraphList) {
const fdxType = paragraph["@_Type"] || "Action";
Expand All @@ -143,15 +154,15 @@ export class FinalDraftAdapter extends ProjectAdapter<BaseExportOptions> {
// Ensure textNodes is always an array
const textList = Array.isArray(textNodes) ? textNodes : [textNodes];

const content: any[] = [];
const content: JSONContent[] = [];

for (const textNode of textList) {
// Handle both string content and object with #text
const text = typeof textNode === "string" ? textNode : textNode["#text"] || "";

if (!text) continue;

const jsonNode: any = {
const jsonNode: JSONContent = {
type: "text",
text: text,
};
Expand All @@ -160,7 +171,7 @@ export class FinalDraftAdapter extends ProjectAdapter<BaseExportOptions> {
const styleAttr = typeof textNode === "object" ? textNode["@_Style"] : undefined;
if (styleAttr) {
const styles = styleAttr.split("+");
const marks: any[] = [];
const marks: { type: string }[] = [];

for (const style of styles) {
const markType = FDX_STYLE_REVERSE[style];
Expand Down
7 changes: 3 additions & 4 deletions src/lib/adapters/fountain/fountain-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Screenplay } from "../../utils/types";
import { BaseExportOptions, ProjectAdapter } from "../screenplay-adapter";

import fountain from "./fountain_parser";
Expand Down Expand Up @@ -58,7 +57,7 @@ export class FountainAdapter extends ProjectAdapter {

// Check if this line contains a format node
const formatChild = content.find(
(c: any) => c.type === "tp-title" || c.type === "tp-author" || c.type === "tp-date",
(c: JSONContent) => c.type === "tp-title" || c.type === "tp-author" || c.type === "tp-date",
);

if (formatChild) {
Expand All @@ -71,7 +70,7 @@ export class FountainAdapter extends ProjectAdapter {

// Plain text line — flatten and use as Credit
const text = content
.map((c: any) => c.text ?? "")
.map((c: JSONContent) => c.text ?? "")
.join("")
.trim();
if (text) {
Expand All @@ -88,7 +87,7 @@ export class FountainAdapter extends ProjectAdapter {
let fountain = this.buildFountainTitlePage(project, options);

let sceneCount = 1;
let nodes = project.screenplay();
const nodes = project.screenplay();
const characters = options.characters;

for (let i = 0; i < nodes.length; i++) {
Expand Down
119 changes: 70 additions & 49 deletions src/lib/adapters/fountain/fountain_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@

"use strict";

interface FountainToken {
type: string;
text?: string;
scene_number?: string;
depth?: number;
dual?: "left" | "right";
}

interface FountainOutput {
title?: string;
html: { title_page: string; script: string };
tokens?: FountainToken[];
}

type FountainCallback = (output: FountainOutput) => unknown;

interface FountainParser {
(script: string, callback?: FountainCallback): FountainOutput;
parse: (script: string, toks?: boolean | FountainCallback, callback?: FountainCallback) => FountainOutput;
}

const regex = {
title_page: /^((?:title|credit|author[s]?|source|notes|draft date|date|contact|copyright)\:)/gim,

Expand Down Expand Up @@ -52,17 +73,17 @@ const lexer = function (script: string) {
};

const tokenize = function (script: string) {
var src = lexer(script).split(regex.splitter),
i = src.length,
line: string,
match: RegExpMatchArray | null,
parts: string[],
text: string,
meta: string | undefined,
x: number,
xlen: number,
dual: boolean | undefined,
tokens: any[] = [];
const src = lexer(script).split(regex.splitter);
let i = src.length;
let line: string;
let match: RegExpMatchArray | null;
let parts: string[];
let text: string;
let meta: string | undefined;
let x: number;
let xlen: number;
let dual: boolean | undefined;
const tokens: FountainToken[] = [];

while (i--) {
line = src[i];
Expand All @@ -85,9 +106,9 @@ const tokenize = function (script: string) {

// title page
if (regex.title_page.test(line)) {
match = line.replace(regex.title_page, "\n$1").split(regex.splitter).reverse() as any;
for (x = 0, xlen = (match as any[]).length; x < xlen; x++) {
parts = (match as any[])[x].replace(regex.cleaner, "").split(/\:\n*/);
const titleParts = line.replace(regex.title_page, "\n$1").split(regex.splitter).reverse();
for (x = 0, xlen = titleParts.length; x < xlen; x++) {
parts = titleParts[x].replace(regex.cleaner, "").split(/\:\n*/);
tokens.push({
type: parts[0].trim().toLowerCase().replace(" ", "_"),
text: parts[1].trim(),
Expand Down Expand Up @@ -152,7 +173,7 @@ const tokenize = function (script: string) {
}

// Strip @ prefix for forced character names
var characterName = match[1].trim();
let characterName = match[1].trim();
if (characterName.charAt(0) === "@") characterName = characterName.substring(1);

// If (CONT'D) is contained in character name, remove it
Expand Down Expand Up @@ -215,7 +236,7 @@ const tokenize = function (script: string) {
return tokens;
};

const inline: Record<string, any> = {
const inline: Record<string, string | ((s: string) => string | undefined)> = {
//note: '<span class="note">$1</span>',
line_break: "<br />",

Expand All @@ -233,31 +254,31 @@ inline.lexer = function (s: string) {
return;
}

var styles = [
"underline",
"italic",
"bold",
"bold_italic",
"italic_underline",
"bold_underline",
"bold_italic_underline",
],
i = styles.length,
style: string,
match;
const styles = [
"underline",
"italic",
"bold",
"bold_italic",
"italic_underline",
"bold_underline",
"bold_italic_underline",
];
let i = styles.length;
let style: string;
let match: RegExp;

s = s
.replace(regex.note_inline, inline.note)
.replace(regex.note_inline, inline.note as string)
.replace(/\\\*/g, "[star]")
.replace(/\\_/g, "[underline]")
.replace(/\n/g, inline.line_break);
.replace(/\n/g, inline.line_break as string);

while (i--) {
style = styles[i];
match = (regex as any)[style];
match = (regex as Record<string, RegExp>)[style];

if (match.test(s)) {
s = s.replace(match, inline[style]);
s = s.replace(match, inline[style] as string);
}
}

Expand All @@ -267,28 +288,28 @@ inline.lexer = function (s: string) {
.trim();
};

const parse = function (script: string, toks?: any, callback?: Function) {
const parse = function (script: string, toks?: boolean | FountainCallback, callback?: FountainCallback): FountainOutput {
if (callback === undefined && typeof toks === "function") {
callback = toks;
toks = undefined;
}

var tokens = tokenize(script),
i = tokens.length,
token,
title: string | undefined,
title_page: string[] = [],
html: string[] = [],
output;
const tokens = tokenize(script);
let i = tokens.length;
let token: FountainToken;
let title: string | undefined;
const title_page: string[] = [];
const html: string[] = [];
let output: FountainOutput;

while (i--) {
token = tokens[i];
token.text = inline.lexer(token.text);
token.text = inline.lexer(token.text ?? "") as string | undefined;

switch (token.type) {
case "title":
title_page.push("<h1>" + token.text + "</h1>");
title = token.text.replace("<br />", " ").replace(/<(?:.|\n)*?>/g, "");
title = token.text?.replace("<br />", " ").replace(/<(?:.|\n)*?>/g, "");
break;
case "credit":
title_page.push('<p class="credit">' + token.text + "</p>");
Expand Down Expand Up @@ -324,7 +345,7 @@ const parse = function (script: string, toks?: any, callback?: Function) {
);
break;
case "transition":
if (token.text.charAt(token.text.length - 1) === ":") {
if (token.text?.charAt(token.text.length - 1) === ":") {
token.text = token.text.slice(0, -1);
}

Expand All @@ -334,10 +355,10 @@ const parse = function (script: string, toks?: any, callback?: Function) {
html.push('<p class="character">' + token.text + "</p>");
break;
case "parenthetical":
if (token.text.charAt(token.text.length - 1) === ")") {
if (token.text?.charAt(token.text.length - 1) === ")") {
token.text = token.text.slice(0, -1);
}
if (token.text.charAt(0) === "(") {
if (token.text?.charAt(0) === "(") {
token.text = token.text.slice(1);
}
html.push('<p class="parenthetical">' + token.text + "</p>");
Expand Down Expand Up @@ -384,17 +405,17 @@ const parse = function (script: string, toks?: any, callback?: Function) {
};

if (typeof callback === "function") {
return callback(output);
return callback(output) as FountainOutput;
}

return output;
};

const fountain: any = function (script: string, callback?: Function) {
const fountain = function (script: string, callback?: FountainCallback): FountainOutput {
return fountain.parse(script, callback);
};
} as FountainParser;

fountain.parse = function (script: string, tokens?: any, callback?: Function) {
fountain.parse = function (script: string, tokens?: boolean | FountainCallback, callback?: FountainCallback): FountainOutput {
return parse(script, tokens, callback);
};

Expand Down
5 changes: 3 additions & 2 deletions src/lib/adapters/pdf/pdf-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BaseExportOptions, ProjectAdapter } from "../screenplay-adapter";
import { ProjectData, ProjectState } from "@src/lib/project/project-state";
import { PageFormat } from "@src/lib/utils/enums";
import { getFontForCodePoint, ScriptFont } from "./pdf-utils";
import type { TextRun } from "./pdf.worker";
import { BASE_URL } from "@src/lib/utils/constants";
import { PAGE_SIZES } from "@src/lib/screenplay/extensions/pagination-extension";

Expand Down Expand Up @@ -120,7 +121,7 @@ export class PDFAdapter extends ProjectAdapter<PDFExportOptions> {
});
}

convertFrom(_rawContent: ArrayBuffer): Partial<ProjectData> {
convertFrom(_: ArrayBuffer): Partial<ProjectData> {
throw new Error("Method not implemented.");
}

Expand Down Expand Up @@ -334,7 +335,7 @@ export class PDFAdapter extends ProjectAdapter<PDFExportOptions> {
const uppercase = getComputedStyle(el).textTransform === "uppercase";

let currentLine: VisualLine | null = null;
let currentRun: any | null = null;
let currentRun: TextRun | null = null;
let previousY = -1;

let textNode: Text | null;
Expand Down
Loading
Loading