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
6 changes: 3 additions & 3 deletions manifest-beta.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "longform",
"name": "Longform",
"version": "2.0.0",
"id": "longform-paperbell",
"name": "Longform (PaperBell)",
"version": "2.2.0-beta.4",
"minAppVersion": "1.0",
"description": "Write novels, screenplays, and other long projects in Obsidian.",
"author": "Kevin Barrett",
Expand Down
6 changes: 3 additions & 3 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "longform",
"name": "Longform",
"version": "2.2.0-beta.3",
"id": "longform-paperbell",
"name": "Longform (PaperBell)",
"version": "2.2.0-beta.4",
"minAppVersion": "1.0",
"description": "Write novels, screenplays, and other long projects in Obsidian.",
"author": "Kevin Barrett",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "longform",
"version": "2.2.0-beta.3",
"version": "2.2.0-beta.4",
"description": "Write novels, screenplays, and other long projects in Obsidian (https://obsidian.md).",
"main": "main.js",
"scripts": {
Expand Down
4 changes: 3 additions & 1 deletion src/compile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ export async function compile(
draft: Draft,
workflow: Workflow,
kinds: CompileStepKind[],
statusCallback: (status: CompileStatus) => void
statusCallback: (status: CompileStatus) => void,
options?: { suppressOpenAfter?: boolean }
): Promise<void> {
let currentInput: any;

Expand Down Expand Up @@ -249,6 +250,7 @@ export async function compile(
utilities: {
normalizePath,
},
suppressOpenAfter: options?.suppressOpenAfter,
};

console.log(
Expand Down
5 changes: 5 additions & 0 deletions src/compile/steps/abstract-compile-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ export type CompileContext = {
/** Obsidian’s normalizePath function. Converts an arbitrary file path string into a normalized one. */
normalizePath: (path: string) => string;
};
/**
* When true, steps that would open the compiled result in a new pane should skip doing so.
* Set during batch ("Compile All Drafts") runs to avoid spawning a pane per draft.
*/
suppressOpenAfter?: boolean;
};

/**
Expand Down
15 changes: 12 additions & 3 deletions src/compile/steps/write-to-note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const WriteToNoteStep = makeBuiltinStep({
id: "target",
name: "Output path",
description:
"Path for the created manuscript note. Paths starting with '/' are relative to your vault root; otherwise relative to your project. $1 will be replaced with your project's title.",
"Path for the created manuscript note. Paths starting with '/' are relative to your vault root; otherwise relative to your project. $1 is replaced with your project's title; $2 is replaced with this draft's name (e.g. \"compiled/$2.md\" gives each draft its own file).",
type: CompileStepOptionType.Text,
default: "manuscript.md",
},
Expand All @@ -37,8 +37,17 @@ export const WriteToNoteStep = makeBuiltinStep({
if (context.kind !== CompileStepKind.Manuscript) {
throw new Error("Cannot write non-manuscript as note.");
} else {
const indexBasename = context.draft.vaultPath
.split("/")
.last()
.replace(/\.md$/, "");
const draftName = context.draft.draftTitle ?? indexBasename;
let target = context.optionValues["target"] as string;
target = target.replace("$1", context.draft.title);
target = target
.split("$2")
.join(draftName)
.split("$1")
.join(context.draft.title);

const openAfter = context.optionValues["open-after"] as boolean;
if (!target || target.length == 0) {
Expand All @@ -48,7 +57,7 @@ export const WriteToNoteStep = makeBuiltinStep({
const filePath = resolvePath(context.projectPath, target);
await writeToFile(context.app, filePath, input.contents);

if (openAfter) {
if (openAfter && !context.suppressOpenAfter) {
console.log("[Longform] Attempting to open:", filePath);

context.app.workspace.openLinkText(filePath, "/", true).catch((err) => {
Expand Down
103 changes: 95 additions & 8 deletions src/view/compile/CompileView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
selectedDraft,
workflows,
currentWorkflow,
selectedProject,
selectedProjectHasMultipleDrafts,
} from "src/model/stores";
import { draftTitle } from "src/model/draft-utils";
import CompileStepView from "./CompileStepView.svelte";
import SortableList from "../sortable/SortableList.svelte";
import AutoTextArea from "../components/AutoTextArea.svelte";
Expand Down Expand Up @@ -241,8 +244,12 @@
draft: Draft,
workflow: Workflow,
kinds: CompileStepKind[],
statusCallback: (status: CompileStatus) => void
) => Vault = getContext("compile");
statusCallback: (status: CompileStatus) => void,
options?: { suppressOpenAfter?: boolean }
) => Promise<void> = getContext("compile");

let isCompiling = false;

function doCompile() {
compile(
$selectedDraft,
Expand All @@ -251,6 +258,68 @@
onCompileStatusChange
);
}

async function doCompileAll() {
const projectDrafts = $selectedProject ?? [];
if (projectDrafts.length === 0) {
return;
}

isCompiling = true;
let compiledCount = 0;

for (let i = 0; i < projectDrafts.length; i++) {
const draft = projectDrafts[i];
const label = `${draftTitle(draft)} (${i + 1}/${projectDrafts.length})`;

const workflow = draft.workflow
? $workflows[draft.workflow]
: $currentWorkflow;
if (!workflow) {
new Notice(`Skipped ${label}: no workflow assigned.`);
continue;
}

const [validationResult, kinds] = calculateWorkflow(
workflow,
draft.format === "scenes"
);
if (validationResult.error !== WorkflowError.Valid) {
new Notice(`Skipped ${label}: ${validationResult.error}`);
continue;
}

// Prefix per-step status with which draft we're on; swallow the per-draft
// success notice so we only announce once at the end.
const wrappedStatus = (status: CompileStatus) => {
if (status.kind === "CompileStatusStep") {
compileStatus.innerText = `Compiling ${label} — step ${
status.stepIndex + 1
}/${status.totalSteps}`;
} else if (status.kind === "CompileStatusError") {
onCompileStatusChange(status);
}
};

try {
await compile(draft, workflow, kinds, wrappedStatus, {
suppressOpenAfter: true,
});
compiledCount++;
} catch (error) {
console.error("[Longform]", error);
new Notice(`Failed to compile ${label}. See console for details.`);
}
}

isCompiling = false;
compileStatus.innerText = `Compiled ${compiledCount} draft${
compiledCount === 1 ? "" : "s"
}.`;
compileStatus.classList.add("compile-status-success");
restoreDefaultStatusAfter();
new Notice(`Compiled ${compiledCount} draft${compiledCount === 1 ? "" : "s"}.`);
}
</script>

{#if $selectedDraft}
Expand Down Expand Up @@ -369,12 +438,23 @@

<div class="longform-compile-run-container">
{#if $currentWorkflow && $currentWorkflow.steps.length > 0}
<button
class="compile-button"
on:click={doCompile}
disabled={validation.error !== WorkflowError.Valid}
aria-label={validation.error}>Compile</button
>
<div class="longform-compile-buttons">
<button
class="compile-button"
on:click={doCompile}
disabled={validation.error !== WorkflowError.Valid || isCompiling}
aria-label={validation.error}>Compile</button
>
{#if $selectedProjectHasMultipleDrafts}
<button
class="compile-button"
on:click={doCompileAll}
disabled={isCompiling}
title="Compile every draft in this project, each to its own file."
>Compile All Drafts</button
>
{/if}
</div>
<span class="compile-status" bind:this={compileStatus}
>{validation.error === WorkflowError.Valid
? defaultCompileStatus
Expand Down Expand Up @@ -507,6 +587,13 @@
margin-top: var(--size-4-8);
}

.longform-compile-buttons {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--size-4-2);
}

.longform-compile-run-container .compile-status {
color: var(--text-muted);
}
Expand Down
12 changes: 10 additions & 2 deletions src/view/explorer/ExplorerPane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,17 @@ export class ExplorerPane extends ItemView {
draft: Draft,
workflow: Workflow,
kinds: CompileStepKind[],
statusCallback: (status: CompileStatus) => void
statusCallback: (status: CompileStatus) => void,
options?: { suppressOpenAfter?: boolean }
) => {
compile(this.app, draft, workflow, kinds, statusCallback);
return compile(
this.app,
draft,
workflow,
kinds,
statusCallback,
options
);
}
);

Expand Down
2 changes: 1 addition & 1 deletion test-longform-vault/.obsidian/community-plugins.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[
"longform",
"longform-paperbell",
"templater-obsidian"
]
13 changes: 13 additions & 0 deletions test-longform-vault/submission-project/Cover Letter (Index).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
longform:
format: scenes
title: PaperDraft
draftTitle: Cover Letter
workflow: Default Workflow
sceneFolder: cover
scenes:
- cover letter
ignoredFiles: []
---

This is the cover letter draft, a single act addressed to the editor. It shares the same `metadata.json` as the rest of the submission package.
16 changes: 16 additions & 0 deletions test-longform-vault/submission-project/Main Manuscript (Index).md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
longform:
format: scenes
title: PaperDraft
draftTitle: Main Manuscript
workflow: Default Workflow
sceneFolder: manuscript
scenes:
- introduction
- methods
- results
- discussion and conclusion
ignoredFiles: []
---

This is the main manuscript draft of the submission package. It is organized in four acts and shares its publication metadata with the other drafts via `metadata.json` in this folder.
44 changes: 44 additions & 0 deletions test-longform-vault/submission-project/PaperDraft_Cover Letter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: "A Complete Submission Package"
date: "2026-06-06"
authors:
- name: "Doe, Jane"
affiliation: [1, 2]
corresponding: "yes"
- name: "Roe, Rick"
affiliation: [3]
affiliations:
- index: 1
name: "Institute for Worked Examples"
- index: 2
name: "Aspen Institute"
- index: 3
name: "Center for Placeholder Studies"
abstract: "A worked example bundling a four-act main manuscript, a two-act supplement, a point-by-point response to reviewers, and a cover letter, used to exercise the Add Zenodo Frontmatter step across multiple drafts that share one metadata file."
keywords:
- "longform"
- "submission"
- "metadata"
- "zenodo"
target: "Journal of Reproducible Examples"
acronym: "ACSP"
csl: "nature"
template: "default"
lineno: "true"
numbersections: true
---

# cover letter

# Cover Letter

Dear Editor,

We are pleased to submit our manuscript, "A Complete Submission Package," for consideration at the Journal of Reproducible Examples. The work presents a coordinated set of documents — main text, supplement, response to reviewers, and this letter — that share a single publication metadata record.

We confirm that the manuscript is original, is not under consideration elsewhere, and that all authors have approved the submission. We have no conflicts of interest to declare.

Thank you for your consideration.

Sincerely,
Jane Doe, on behalf of all authors
Loading
Loading