├── .tool-versions ├── test-longform-vault ├── simple-project │ ├── 1.md │ ├── 10.md │ ├── 11.md │ ├── 13.md │ ├── 14.md │ ├── 15.md │ ├── 16.md │ ├── 17.md │ ├── 18.md │ ├── 19.md │ ├── 2.md │ ├── 20.md │ ├── 4.md │ ├── 6.md │ ├── 7.md │ ├── 8.md │ ├── 9.md │ ├── fifth.md │ ├── fourth.md │ ├── 12.md │ ├── third scene unlisted.md │ ├── 5.md │ ├── second scene.md │ ├── first scene.md │ ├── manuscript.md │ ├── simple-project.md │ └── 3.md ├── .obsidian │ ├── hotkeys.json │ ├── plugins │ │ ├── longform │ │ │ └── .hotreload │ │ ├── obsidian-minimal-settings │ │ │ ├── styles.css │ │ │ └── manifest.json │ │ ├── hot-reload │ │ │ └── manifest.json │ │ ├── templater-obsidian │ │ │ ├── manifest.json │ │ │ └── styles.css │ │ └── obsidian-style-settings │ │ │ └── manifest.json │ ├── community-plugins.json │ ├── templates.json │ ├── snippets │ │ ├── test-longform-font.css │ │ ├── test-status-styling.css │ │ └── test-leaf-styling.css │ ├── app.json │ ├── appearance.json │ ├── core-plugins-migration.json │ └── core-plugins.json ├── projects 2.0 │ ├── A Novel Draft 2 │ │ ├── test2.md │ │ ├── scenes-hello │ │ │ ├── test.md │ │ │ ├── test3.md │ │ │ ├── test4.md │ │ │ └── in this draft.md │ │ ├── test.md │ │ ├── manuscript.md │ │ └── A Novel Draft 2 (Index).md │ ├── A Novel │ │ ├── fourth.md │ │ ├── first scene-scratch.md │ │ ├── first scene.md │ │ ├── manuscript.md │ │ └── A Novel (Index).md │ ├── An Essay.md │ ├── test4 │ │ └── Index.md │ ├── A Short Story.md │ └── test new draft │ │ └── Index.md ├── some non-longform note.md ├── templates │ ├── Scene Core Template.md │ └── Scene Templater Template.md └── scripts │ └── example-user-script.js ├── .prettierignore ├── .vscode └── settings.json ├── .envrc.example ├── docs ├── res │ ├── explorer.png │ ├── reordering.gif │ ├── simple-scenes-list.png │ ├── multi-walkthrough-1.png │ ├── multi-walkthrough-2.png │ ├── single-walkthrough-1.png │ ├── simple-scenes-list-nested.png │ ├── walkthrough-create-multi.png │ ├── walkthrough-create-single.png │ ├── walkthrough-multi-fresh-pane.png │ ├── arbitrarily-nested-scenes-list.png │ └── walkthrough-create-longform-project.png ├── SINGLE_SCENE_PROJECTS.md ├── WORD_COUNTS.md ├── MIGRATING_FROM_VERSION_1_TO_2.md ├── COMMANDS.md ├── INDEX_FILE.md └── MULTIPLE_SCENE_PROJECTS.md ├── src ├── view │ ├── sortable │ │ ├── sortable.d.ts │ │ └── SortableList.svelte │ ├── components │ │ ├── Icon.svelte │ │ ├── Disclosure.svelte │ │ └── AutoTextArea.svelte │ ├── undo │ │ └── undo-manager.ts │ ├── compile │ │ └── add-step-modal │ │ │ ├── index.ts │ │ │ └── AddStepModal.svelte │ ├── utils.ts │ ├── settings │ │ ├── file-suggest.ts │ │ ├── folder-suggest.ts │ │ └── suggest.ts │ ├── ConfirmActionModal.ts │ ├── explorer │ │ ├── NewSceneField.svelte │ │ ├── Tab.svelte │ │ ├── scene-menu-items.ts │ │ ├── DraftList.svelte │ │ ├── ProjectPicker.svelte │ │ └── ExplorerView.svelte │ ├── stores.ts │ ├── project-lifecycle │ │ ├── new-draft-modal │ │ │ ├── index.ts │ │ │ └── NewDraftModal.svelte │ │ └── new-project-modal │ │ │ ├── index.ts │ │ │ └── NewProjectModal.svelte │ └── icon.ts ├── utils │ └── file-utils.ts ├── commands │ ├── types.ts │ ├── word-counts.ts │ ├── index.ts │ ├── indentation.ts │ ├── helpers.ts │ ├── templates.ts │ └── compile.ts ├── compile │ ├── steps │ │ ├── index.ts │ │ ├── remove-strikethroughs.ts │ │ ├── concatenate-text.ts │ │ ├── add-frontmatter.ts │ │ ├── strip-frontmatter.ts │ │ ├── remove-comments.ts │ │ ├── prepend-title.ts │ │ ├── remove-links.ts │ │ └── write-to-note.ts │ └── serialization.ts ├── model │ ├── stores.ts │ ├── scene-navigation.ts │ ├── types.ts │ ├── migration.ts │ ├── note-utils.ts │ └── draft-utils.ts └── api │ └── LongformAPI.ts ├── .prettierrc ├── svelte.config.js ├── vitest.config.ts ├── manifest-beta.json ├── versions.json ├── manifest.json ├── .github ├── ISSUE_TEMPLATE │ ├── compile-step-request.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── unit-tests.yml ├── styles.css ├── .eslintrc.js ├── tsconfig.json ├── .gitignore ├── package.json ├── LICENSE.md ├── rollup.config.js └── test └── compile └── steps └── remove-links.test.ts /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 16.17.1 2 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/1.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/10.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/11.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/13.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/14.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/15.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/16.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/17.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/18.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/19.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/2.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/20.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/4.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/6.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/7.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/8.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/9.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/fifth.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/hotkeys.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test-longform-vault/simple-project/fourth.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/12.md: -------------------------------------------------------------------------------- 1 | fgfgggfhgfhf -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/plugins/longform/.hotreload: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel Draft 2/test2.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/third scene unlisted.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | main.js 3 | test-longform-vault 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false 3 | } -------------------------------------------------------------------------------- /test-longform-vault/simple-project/5.md: -------------------------------------------------------------------------------- 1 | this is the fifth scene -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel/fourth.md: -------------------------------------------------------------------------------- 1 | hello! asdsadsad -------------------------------------------------------------------------------- /.envrc.example: -------------------------------------------------------------------------------- 1 | export PLUGINS_DIR="/path/to/your/vaults/plugins_dir" 2 | -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel Draft 2/scenes-hello/test.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel Draft 2/test.md: -------------------------------------------------------------------------------- 1 | sadasdsadsadasd -------------------------------------------------------------------------------- /test-longform-vault/simple-project/second scene.md: -------------------------------------------------------------------------------- 1 | This is another scene. -------------------------------------------------------------------------------- /test-longform-vault/some non-longform note.md: -------------------------------------------------------------------------------- 1 | I don't belong to a project! -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel Draft 2/scenes-hello/test3.md: -------------------------------------------------------------------------------- 1 | sadasdsadsad -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel Draft 2/scenes-hello/test4.md: -------------------------------------------------------------------------------- 1 | asdsadasdasd -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel/first scene-scratch.md: -------------------------------------------------------------------------------- 1 | some scratch doc -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/plugins/obsidian-minimal-settings/styles.css: -------------------------------------------------------------------------------- 1 | /* Empty */ -------------------------------------------------------------------------------- /docs/res/explorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/explorer.png -------------------------------------------------------------------------------- /src/view/sortable/sortable.d.ts: -------------------------------------------------------------------------------- 1 | declare module "sortablejs/modular/sortable.core.esm.js"; 2 | -------------------------------------------------------------------------------- /docs/res/reordering.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/reordering.gif -------------------------------------------------------------------------------- /test-longform-vault/templates/Scene Core Template.md: -------------------------------------------------------------------------------- 1 | core template 2 | 3 | {{date}} 4 | 5 | test -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel Draft 2/scenes-hello/in this draft.md: -------------------------------------------------------------------------------- 1 | asdasdsadasd asdsa asd -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/community-plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | "longform", 3 | "templater-obsidian" 4 | ] -------------------------------------------------------------------------------- /docs/res/simple-scenes-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/simple-scenes-list.png -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "folder": "templates", 3 | "dateFormat": "DD-MM-YYYY" 4 | } -------------------------------------------------------------------------------- /docs/res/multi-walkthrough-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/multi-walkthrough-1.png -------------------------------------------------------------------------------- /docs/res/multi-walkthrough-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/multi-walkthrough-2.png -------------------------------------------------------------------------------- /docs/res/single-walkthrough-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/single-walkthrough-1.png -------------------------------------------------------------------------------- /docs/res/simple-scenes-list-nested.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/simple-scenes-list-nested.png -------------------------------------------------------------------------------- /docs/res/walkthrough-create-multi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/walkthrough-create-multi.png -------------------------------------------------------------------------------- /docs/res/walkthrough-create-single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/walkthrough-create-single.png -------------------------------------------------------------------------------- /docs/res/walkthrough-multi-fresh-pane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/walkthrough-multi-fresh-pane.png -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/snippets/test-longform-font.css: -------------------------------------------------------------------------------- 1 | .longform-explorer { 2 | --longform-explorer-font-size: 16px; 3 | } -------------------------------------------------------------------------------- /docs/res/arbitrarily-nested-scenes-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/arbitrarily-nested-scenes-list.png -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "legacyEditor": false, 3 | "livePreview": true, 4 | "showUnsupportedFiles": true 5 | } -------------------------------------------------------------------------------- /test-longform-vault/simple-project/first scene.md: -------------------------------------------------------------------------------- 1 | A simple [Longform](https://github.com/kevboh/longform/) project with the default settings. -------------------------------------------------------------------------------- /docs/res/walkthrough-create-longform-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevboh/longform/HEAD/docs/res/walkthrough-create-longform-project.png -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/snippets/test-status-styling.css: -------------------------------------------------------------------------------- 1 | [data-scene-status="done"]::before { 2 | content: "🎉"; 3 | padding: 0 4px 0 0; 4 | } 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [], 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "es5" 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/file-utils.ts: -------------------------------------------------------------------------------- 1 | import { TAbstractFile, TFile } from "obsidian"; 2 | 3 | export function isFile(file: TAbstractFile): boolean { 4 | return file instanceof TFile; 5 | } 6 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const preprocess = require("svelte-preprocess"); 3 | 4 | module.exports = { 5 | emitCss: false, 6 | preprocess: preprocess(), 7 | }; 8 | -------------------------------------------------------------------------------- /src/commands/types.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from "obsidian"; 2 | 3 | import type LongformPlugin from "src/main"; 4 | 5 | export type CommandBuilder = (plugin: LongformPlugin) => Command; 6 | -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/snippets/test-leaf-styling.css: -------------------------------------------------------------------------------- 1 | .longform-leaf { 2 | color: red; 3 | } 4 | 5 | .longform-leaf .markdown-source-view { 6 | --font-text: Baskerville; 7 | color: red; 8 | } -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/An Essay.md: -------------------------------------------------------------------------------- 1 | --- 2 | longform: 3 | format: single 4 | draftNumber: 1 5 | workflow: test 6 | --- 7 | 8 | This is my cool essay. It's a Longform project all on its own! -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | coverage: { 6 | reporter: ["text", "html"], 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /test-longform-vault/templates/Scene Templater Template.md: -------------------------------------------------------------------------------- 1 | --- 2 | creation date: <% tp.file.creation_date() %> modification date: <% tp.file.last_modified_date("dddd Do MMMM YYYY HH:mm:ss") %> 3 | --- 4 | 5 | Templater template -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Novel Draft 2/manuscript.md: -------------------------------------------------------------------------------- 1 | # in this draft 2 | 3 | asdasdsadasd asdsa asd 4 | 5 | --- 6 | 7 | # test 8 | 9 | 10 | 11 | --- 12 | 13 | # test3 14 | 15 | sadasdsadsad 16 | 17 | --- 18 | 19 | # test4 20 | 21 | asdsadasdasd -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/appearance.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseFontSize": 16, 3 | "theme": "obsidian", 4 | "translucency": false, 5 | "enabledCssSnippets": [ 6 | "writing", 7 | "test-longform-font", 8 | "test-status-styling" 9 | ], 10 | "cssTheme": "", 11 | "accentColor": "" 12 | } -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/plugins/hot-reload/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hot-reload", 3 | "name": "Hot Reload", 4 | "version": "0.1.10", 5 | "minAppVersion": "0.15.9", 6 | "description": "Automatically reload in-development plugins when their files are changed", 7 | "isDesktopOnly": true 8 | } 9 | -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/test4/Index.md: -------------------------------------------------------------------------------- 1 | --- 2 | longform: 3 | format: scenes 4 | title: A Novel; or, What Projects Look Like Now 5 | draftTitle: test4 6 | workflow: test 7 | sceneFolder: / 8 | scenes: [] 9 | ignoredFiles: 10 | - "*-scratch" 11 | - manuscript 12 | --- 13 | 14 | -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/A Short Story.md: -------------------------------------------------------------------------------- 1 | --- 2 | longform: 3 | format: single 4 | workflow: Default Workflow 5 | --- 6 | 7 | This is a short story, and an example of a single-note Longform project. The entire project is this note. It should display differently from multi-note, more traditional Longform projects. -------------------------------------------------------------------------------- /src/commands/word-counts.ts: -------------------------------------------------------------------------------- 1 | import type { CommandBuilder } from "./types"; 2 | 3 | export const startNewSession: CommandBuilder = (plugin) => ({ 4 | id: "longform-start-new-session", 5 | name: "Start new writing session", 6 | callback: () => { 7 | plugin.writingSessionTracker.startNewSession(); 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /test-longform-vault/projects 2.0/test new draft/Index.md: -------------------------------------------------------------------------------- 1 | --- 2 | longform: 3 | format: scenes 4 | title: A Novel; or, What Projects Look Like Now 5 | draftTitle: test new draft 6 | workflow: test 7 | sceneFolder: / 8 | scenes: [] 9 | ignoredFiles: 10 | - "*-scratch" 11 | - manuscript 12 | --- 13 | 14 | -------------------------------------------------------------------------------- /manifest-beta.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "longform", 3 | "name": "Longform", 4 | "version": "2.0.0", 5 | "minAppVersion": "1.0", 6 | "description": "Write novels, screenplays, and other long projects in Obsidian.", 7 | "author": "Kevin Barrett", 8 | "authorUrl": "https://kevinbarrett.org", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/manuscript.md: -------------------------------------------------------------------------------- 1 | #: 1 second scene 2 | 3 | This is another scene. 4 | 5 | --- 6 | 7 | #: 2 third scene unlisted 8 | 9 | 10 | 11 | --- 12 | 13 | ##: 2.1 fourth 14 | 15 | 16 | 17 | --- 18 | 19 | ##: 2.2 first scene 20 | 21 | A simple Longform project with the default settings. 22 | 23 | --- 24 | 25 | #: 3 fifth 26 | 27 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "2.1.0": "1.3.5", 3 | "2.0.7": "1.3.5", 4 | "2.0.6": "1.3.5", 5 | "2.0.5": "1.1.9", 6 | "2.0.4": "1.1.9", 7 | "2.0.3": "1.1.9", 8 | "2.0.2": "1.0", 9 | "2.0.1": "1.0", 10 | "2.0.0": "1.0", 11 | "1.1.0": "0.12.11", 12 | "1.0.3": "0.12.11", 13 | "1.0.2": "0.12.11", 14 | "1.0.1": "0.12.0", 15 | "1.0.0": "0.12.0" 16 | } 17 | -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/plugins/templater-obsidian/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "templater-obsidian", 3 | "name": "Templater", 4 | "version": "1.16.0", 5 | "description": "Create and use templates", 6 | "minAppVersion": "0.11.13", 7 | "author": "SilentVoid", 8 | "authorUrl": "https://github.com/SilentVoid13", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "longform", 3 | "name": "Longform", 4 | "version": "2.1.0", 5 | "minAppVersion": "1.0", 6 | "description": "Write novels, screenplays, and other long projects in Obsidian.", 7 | "author": "Kevin Barrett", 8 | "authorUrl": "https://kevinbarrett.org", 9 | "fundingUrl": "https://github.com/sponsors/kevboh", 10 | "isDesktopOnly": false 11 | } 12 | -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/plugins/obsidian-minimal-settings/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-minimal-settings", 3 | "name": "Minimal Theme Settings", 4 | "version": "5.3.2", 5 | "minAppVersion": "0.14.15", 6 | "description": "Change the colors, fonts and features of Minimal Theme.", 7 | "author": "@kepano", 8 | "authorUrl": "https://www.twitter.com/kepano", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/compile-step-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Compile step request 3 | about: Suggest a new built-in compile step 4 | title: 'Compile Step: Your step name here' 5 | labels: compile, enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What should this step do?** 11 | For example, I'd like a step that removes all uses of the letter `e`. 12 | 13 | **Should this step affect scenes, manuscripts, or join the two?** 14 | Answer here! 15 | -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/plugins/obsidian-style-settings/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-style-settings", 3 | "name": "Style Settings", 4 | "version": "0.4.10", 5 | "minAppVersion": "0.11.5", 6 | "description": "Offers controls for adjusting theme, plugin, and snippet CSS variables.", 7 | "author": "mgmeyers", 8 | "authorUrl": "https://github.com/mgmeyers/obsidian-style-settings", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --longform-explorer-font-size: var(--font-ui-medium); 3 | --longform-explorer-indent-size: 2em; 4 | } 5 | 6 | .longform-settings-user-steps { 7 | padding-inline-start: 1em; 8 | margin-block-start: 0; 9 | margin-block-end: 0; 10 | } 11 | 12 | .longform-settings-user-step-name { 13 | color: var(--text-normal); 14 | } 15 | 16 | .longform-settings-user-step-id { 17 | margin-left: var(--size-4-2); 18 | color: var(--text-muted); 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Feature Request: Your title here' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | plugins: ["@typescript-eslint"], 5 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 6 | rules: { 7 | "@typescript-eslint/no-unused-vars": [ 8 | 2, 9 | { args: "all", argsIgnorePattern: "^_" }, 10 | ], 11 | "@typescript-eslint/ban-ts-comment": 0, 12 | "@typescript-eslint/no-explicit-any": 0, 13 | "eol-last": ["error", "always"], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "include": ["src/**/*", "test/**/*"], 4 | "exclude": ["node_modules/*"], 5 | "compilerOptions": { 6 | "baseUrl": ".", 7 | "types": ["node", "svelte"], 8 | "sourceMap": true, 9 | "module": "ESNext", 10 | "target": "es6", 11 | "allowJs": true, 12 | "noImplicitAny": true, 13 | "moduleResolution": "node", 14 | "importHelpers": true, 15 | "lib": ["dom", "es5", "scripthost", "es2015"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | 4 | # tests 5 | coverage 6 | 7 | # build 8 | main.js 9 | *.js.map 10 | 11 | # obsidian 12 | data.json 13 | 14 | # dev 15 | /.envrc 16 | 17 | # macos 18 | *.DS_Store 19 | 20 | # test vault 21 | test-longform-vault/.obsidian/workspace 22 | test-longform-vault/.obsidian/plugins/longform/*.css 23 | test-longform-vault/.obsidian/plugins/longform/*.js 24 | test-longform-vault/.obsidian/plugins/longform/*.json 25 | /test-longform-vault/.obsidian/workspace.json 26 | /test-longform-vault/longform-sessions.json 27 | -------------------------------------------------------------------------------- /src/view/components/Icon.svelte: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | {#if iconName.length > 0} 13 | 14 | {/if} 15 | 16 | 23 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/simple-project.md: -------------------------------------------------------------------------------- 1 | --- 2 | longform: 3 | format: scenes 4 | workflow: Default Workflow 5 | sceneFolder: / 6 | scenes: 7 | - "5" 8 | - "1" 9 | - "4" 10 | - "20" 11 | - "2" 12 | - "3" 13 | - "19" 14 | - fourth 15 | - third scene unlisted 16 | - "13" 17 | - second scene 18 | - "12" 19 | - first scene 20 | - "8" 21 | - fifth 22 | - "6" 23 | - "7" 24 | - "10" 25 | - "11" 26 | - "14" 27 | - "15" 28 | - "9" 29 | - "17" 30 | - "16" 31 | - "18" 32 | ignoredFiles: 33 | - manuscript 34 | some-test: hello 35 | --- 36 | 37 | -------------------------------------------------------------------------------- /docs/SINGLE_SCENE_PROJECTS.md: -------------------------------------------------------------------------------- 1 | # Single-scene Projects in Longform 2 | 3 | In Longform, a project can either be single-scene or [multi-scene](./MULTIPLE_SCENE_PROJECTS.md). A single-scene project is comprised of a single [index file](./INDEX_FILE.md) which is also the contents of your project. There are no additional notes (unlike in multi-scene projects, which have many scenes). 4 | 5 | The idea is that this format is useful for (perhaps ironically) shorter-form projects, like short stories or essays. Projects you’d like to edit and compile within Longform but that don’t require a series of related notes. 6 | 7 | These projects are available in the Longform pane like any other project, but do not have a Scenes tab. 8 | -------------------------------------------------------------------------------- /src/view/undo/undo-manager.ts: -------------------------------------------------------------------------------- 1 | import type { KeymapContext } from "obsidian"; 2 | 3 | /** 4 | * Return `false` to automatically preventDefault 5 | */ 6 | export type UndoListener = ( 7 | type: "undo" | "redo", 8 | evt: KeyboardEvent, 9 | ctx: KeymapContext 10 | ) => boolean; 11 | 12 | export class UndoManager { 13 | listeners: UndoListener[] = []; 14 | 15 | destroy() { 16 | this.listeners = []; 17 | } 18 | 19 | on(listener: UndoListener): void { 20 | this.listeners.push(listener); 21 | } 22 | 23 | send(type: "undo" | "redo", evt: KeyboardEvent, ctx: KeymapContext) { 24 | for (const listener of this.listeners) { 25 | listener(type, evt, ctx); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/compile/steps/index.ts: -------------------------------------------------------------------------------- 1 | import { ConcatenateTextStep } from "./concatenate-text"; 2 | import { PrependTitleStep } from "./prepend-title"; 3 | import { RemoveCommentsStep } from "./remove-comments"; 4 | import { RemoveLinksStep } from "./remove-links"; 5 | import { RemoveStrikethroughsStep } from "./remove-strikethroughs"; 6 | import { StripFrontmatterStep } from "./strip-frontmatter"; 7 | import { WriteToNoteStep } from "./write-to-note"; 8 | import { AddFrontmatterStep } from "./add-frontmatter"; 9 | 10 | export const BUILTIN_STEPS = [ 11 | AddFrontmatterStep, 12 | ConcatenateTextStep, 13 | PrependTitleStep, 14 | RemoveCommentsStep, 15 | RemoveLinksStep, 16 | RemoveStrikethroughsStep, 17 | StripFrontmatterStep, 18 | WriteToNoteStep, 19 | ]; 20 | -------------------------------------------------------------------------------- /test-longform-vault/simple-project/3.md: -------------------------------------------------------------------------------- 1 | sdfsdfdsf sdf sdf sdf sdf sdf sdf sdf sdf sf sdf sdf sf sf sf sd fsd fsdfsdfsdf sdf s sdfsdfdsf sdf sdf sdf sdf sdf sdf sdf sdf sf sdf sdf sf sf sf sd fsd fsdfsdfsdf sdf s sdfsdfdsf sdf sdf sdf sdf sdf sdf sdf sdf sf sdf sdf sf sf sf sd fsd fsdfsdfsdf sdf s sdfsdfdsf sdf sdf sdf sdf sdf sdf sdf sdf sf sdf sdf sf sf sf sd fsd fsdfsdfsdf sdf s sdfsdfdsf sdf sdf sdf sdf sdf sdf sdf sdf sf sdf sdf sf sf sf sd fsd fsdfsdfsdf sdf s sdfsdfdsf sdf sdf sdf sdf sdf sdf sdf sdf sf sdf sdf sf sf sf sd fsd fsdfsdfsdf sdf s sdfsdfdsf sdf sdf sdf sdf sdf sdf sdf sdf sf sdf sdf sf sf sf sd fsd fsdfsdfsdf sdf s sdfsdfdsf sdf sdf sdf sdf sdf sdf sdf sdf sf sdf sdf sf sf sf sd fsd fsdfsdfsdf sdf s asdasdsad asdasd adasdasdas asd asd asd as dsa dsa das das das da sd sad asd a -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/core-plugins-migration.json: -------------------------------------------------------------------------------- 1 | { 2 | "file-explorer": true, 3 | "global-search": true, 4 | "switcher": true, 5 | "graph": true, 6 | "backlink": true, 7 | "outgoing-link": false, 8 | "tag-pane": false, 9 | "page-preview": true, 10 | "daily-notes": false, 11 | "templates": true, 12 | "note-composer": true, 13 | "command-palette": true, 14 | "slash-command": false, 15 | "editor-status": true, 16 | "starred": false, 17 | "markdown-importer": true, 18 | "zk-prefixer": false, 19 | "random-note": false, 20 | "outline": false, 21 | "word-count": true, 22 | "slides": false, 23 | "audio-recorder": false, 24 | "workspaces": false, 25 | "file-recovery": true, 26 | "publish": false, 27 | "sync": true, 28 | "canvas": true, 29 | "bookmarks": true, 30 | "properties": true 31 | } -------------------------------------------------------------------------------- /src/view/compile/add-step-modal/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Modal } from "obsidian"; 2 | 3 | import AddStepModal from "./AddStepModal.svelte"; 4 | import { appContext } from "src/view/utils"; 5 | 6 | export default class AddStepModalContainer extends Modal { 7 | constructor(app: App) { 8 | super(app); 9 | } 10 | 11 | onOpen(): void { 12 | const { contentEl } = this; 13 | 14 | contentEl.createEl("h1", { text: "Add Compile Step to Workflow" }); 15 | const entrypoint = contentEl.createDiv("longform-add-step-root"); 16 | 17 | const context = appContext(this); 18 | context.set("close", () => this.close()); 19 | 20 | new AddStepModal({ 21 | target: entrypoint, 22 | context, 23 | }); 24 | } 25 | 26 | onClose(): void { 27 | const { contentEl } = this; 28 | contentEl.empty(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test-longform-vault/.obsidian/core-plugins.json: -------------------------------------------------------------------------------- 1 | { 2 | "file-explorer": true, 3 | "global-search": true, 4 | "switcher": true, 5 | "graph": true, 6 | "backlink": true, 7 | "outgoing-link": false, 8 | "tag-pane": false, 9 | "page-preview": true, 10 | "daily-notes": false, 11 | "templates": true, 12 | "note-composer": true, 13 | "command-palette": true, 14 | "slash-command": false, 15 | "editor-status": true, 16 | "starred": false, 17 | "markdown-importer": true, 18 | "zk-prefixer": false, 19 | "random-note": false, 20 | "outline": false, 21 | "word-count": true, 22 | "slides": false, 23 | "audio-recorder": false, 24 | "workspaces": false, 25 | "file-recovery": true, 26 | "publish": false, 27 | "sync": true, 28 | "canvas": true, 29 | "bookmarks": true, 30 | "properties": true, 31 | "webviewer": false 32 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve Longform 4 | title: 'Bug: Your title here' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Versions** 11 | Longform version: 12 | Obsidian version: 13 | OS [e.g. macOS, Windows, iOS, Android]: 14 | Theme: 15 | Other plugins that you think might be relevant here: 16 | 17 | **Describe the bug** 18 | A clear and concise description of what the bug is. 19 | 20 | **To Reproduce** 21 | Steps to reproduce the behavior: 22 | 1. Go to '...' 23 | 2. Click on '....' 24 | 3. Scroll down to '....' 25 | 4. See error 26 | 27 | **Expected behavior** 28 | A clear and concise description of what you expected to happen. 29 | 30 | **Screenshots** 31 | If applicable, add screenshots to help explain your problem. 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /src/view/components/Disclosure.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 27 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Unit Tests 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | pull_request: 10 | branches: ["main"] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [20.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: "npm" 28 | - run: npm ci 29 | - run: npm run test:unit 30 | -------------------------------------------------------------------------------- /src/view/utils.ts: -------------------------------------------------------------------------------- 1 | import { App, Modal, Platform, View } from "obsidian"; 2 | import { getContext } from "svelte"; 3 | 4 | export function selectElementContents(el: HTMLElement) { 5 | const range = document.createRange(); 6 | range.selectNodeContents(el); 7 | const sel = window.getSelection(); 8 | sel.removeAllRanges(); 9 | sel.addRange(range); 10 | } 11 | 12 | export function invalidFilenameCharacters(): string { 13 | if (Platform.isWin) { 14 | return '* " \\ / : < > | ?'; 15 | } 16 | return "\\ / :"; 17 | } 18 | 19 | export function isValidFilename(name: string): boolean { 20 | return !invalidFilenameCharacters() 21 | .split(" ") 22 | .some((c) => name.contains(c)); 23 | } 24 | 25 | export function appContext(view: View | Modal): Map { 26 | const context = new Map(); 27 | context.set("app", view.app); 28 | return context; 29 | } 30 | 31 | export function useApp(): App { 32 | return getContext("app") as App; 33 | } 34 | -------------------------------------------------------------------------------- /src/view/settings/file-suggest.ts: -------------------------------------------------------------------------------- 1 | import { TAbstractFile, TFile } from "obsidian"; 2 | 3 | import { TextInputSuggest } from "./suggest"; 4 | 5 | export class FileSuggest extends TextInputSuggest { 6 | getSuggestions(inputStr: string): TFile[] { 7 | const abstractFiles = this.app.vault.getAllLoadedFiles(); 8 | const files: TFile[] = []; 9 | const lowerCaseInputStr = inputStr.toLowerCase(); 10 | 11 | abstractFiles.forEach((file: TAbstractFile) => { 12 | if ( 13 | file instanceof TFile && 14 | file.extension === "md" && 15 | file.path.toLowerCase().contains(lowerCaseInputStr) 16 | ) { 17 | files.push(file); 18 | } 19 | }); 20 | 21 | return files; 22 | } 23 | 24 | renderSuggestion(file: TFile, el: HTMLElement): void { 25 | el.setText(file.path); 26 | } 27 | 28 | selectSuggestion(file: TFile): void { 29 | this.inputEl.value = file.path; 30 | this.inputEl.trigger("input"); 31 | this.close(); 32 | this.inputEl.blur(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/view/components/AutoTextArea.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | 21 | 22 |