Skip to content

Commit

Permalink
Features: Options to delete original and save split notes to parent f…
Browse files Browse the repository at this point in the history
…older + minor fixes (#2)

* Mimimal automated formatting pass.

* Lint fix.

* Save to note's parent folder when folder path setting is empty.

* Add delete original note option.

* Style tweaks for consistency with native plugins.

* Trim earlier to prevent empty notes.

* Prefer const.

* Add documentation for new settings to readme.
  • Loading branch information
kitschpatrol authored Jun 12, 2024
1 parent 5ecd9f6 commit d36eb15
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 76 deletions.
5 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"printWidth": 100,
"tabWidth": 4,
"useTabs": true
}
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,32 @@ Note splitter is an [Obsidian.md](https://obsidian.md) plugin for desktop only.
3. Type `Split by delimiter`
4. Press enter

Splitting a note will not modify the original note. It will create a new notes in an output folder that you specify.
Splitting a note will not modify the original note. (Unless the [delete original](#delete-original) setting is enabled.) It will create a new notes in an output folder that you specify.

### Frontmatter

When splitting a note, this plugin will ignore frontmatter. Only the content after the frontmatter block will be split.

## Settings

### Folder path

Folder to save the split notes to. The default is `note-splitter`.

Setting this field to an empty string will save split notes to the same folder as the original note.

### Delimiter

The delimiter to split by can be configured in the plugin settings. The default delimiter is a new line `\n`.

### Use content as title

If enabled, use the first line of the split section as the name for new notes, handling any name collisions automatically. If disabled, a timestamp is used.

Disabled by default.

### Delete original

If enabled, the original note will be deleted after the split.

Disabled by default.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ if (prod) {
await context.rebuild();
await fs.promises.copyFile(
path.join(path.resolve(), "manifest.json"),
path.join(path.resolve(), "dist", "manifest.json")
path.join(path.resolve(), "dist", "manifest.json"),
);
process.exit(0);
} else {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"format": "prettier --write .",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
Expand All @@ -18,6 +19,7 @@
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"obsidian": "latest",
"prettier": "^3.3.2",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
Expand Down
55 changes: 31 additions & 24 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ interface NoteSplitterSettings {
saveFolderPath: string;
useContentAsTitle: boolean;
delimiter: string;
deleteOriginalNote: boolean;
}

const DEFAULT_SETTINGS: NoteSplitterSettings = {
saveFolderPath: "note-splitter",
useContentAsTitle: false,
delimiter: "\\n",
}

deleteOriginalNote: false,
};

export default class NoteSplitterPlugin extends Plugin {
settings: NoteSplitterSettings;
Expand All @@ -30,12 +31,11 @@ export default class NoteSplitterPlugin extends Plugin {
const file = view.file;
if (file === null) return;

let delimiter = this.settings.delimiter;
//Obsidian will store `\n`` as `\\n` in the settings
delimiter = delimiter.replace(/\\n/g, "\n");
const delimiter = this.settings.delimiter.replace(/\\n/g, "\n");

if (delimiter === "") {
new Notice("No delimiter set. Please set a delimiter in the settings");
new Notice("No delimiter set. Please set a delimiter in the settings.");
return;
}

Expand All @@ -48,36 +48,38 @@ export default class NoteSplitterPlugin extends Plugin {
dataWithoutFrontmatter = dataWithoutFrontmatter.slice(frontmatterEndIndex + 1);
}
if (dataWithoutFrontmatter === "") {
new Notice("No content to split");
new Notice("No content to split.");
return;
}

const splitLines = dataWithoutFrontmatter.split(delimiter).filter((line) =>
line !== ""
);

const splitLines = dataWithoutFrontmatter
.split(delimiter)
.map((line) => line.trim())
.filter((line) => line !== "");

if (splitLines.length === 0) {
new Notice("No content to split");
new Notice("No content to split.");
return;
}

if (splitLines.length === 1) {
new Notice("Only one line found. Nothing to split");
new Notice("Only one line found. Nothing to split.");
return;
}

const folderPath = this.settings.saveFolderPath;
const folderPath =
this.settings.saveFolderPath ||
file.parent?.path ||
this.settings.saveFolderPath;

try {
await this.app.vault.createFolder(folderPath);
} catch (err) {
//Folder already exists
}

for (let i = 0; i < splitLines.length; i++) {
const line = splitLines[i].trim();

let filesCreated = 0;
for (const [i, line] of splitLines.entries()) {
let fileName = line;
if (this.settings.useContentAsTitle) {
fileName = escapeInvalidFileNameChars(fileName);
Expand All @@ -89,16 +91,14 @@ export default class NoteSplitterPlugin extends Plugin {
const filePath = normalizePath(`${folderPath}/${fileName}.md`);

try {
await this.app.vault.create(
filePath, line
);
await this.app.vault.create(filePath, line);
filesCreated++;
} catch (err) {
if (err.message.includes("already exists")) {
const newFilePath = `${folderPath}/Split conflict ${crypto.randomUUID()}.md`;
try {
await this.app.vault.create(
newFilePath, line
);
await this.app.vault.create(newFilePath, line);
filesCreated++;
} catch (err) {
console.error(err);
new Notice(`Error creating file: ${err.message}`);
Expand All @@ -109,12 +109,19 @@ export default class NoteSplitterPlugin extends Plugin {
console.log(err);
}
}
new Notice("Split into " + splitLines.length + " note" + (splitLines.length > 1 ? "s" : ""));

if (filesCreated === splitLines.length && this.settings.deleteOriginalNote) {
await this.app.vault.delete(file);
}

new Notice(
"Split into " + filesCreated + " note" + (filesCreated > 1 ? "s" : "") + ".",
);
},
});
}

onunload() { }
onunload() {}

async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
Expand Down
55 changes: 31 additions & 24 deletions src/obsidian/note-splitter-settings-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { App, PluginSettingTab, Setting } from "obsidian";
import NoteSplitterPlugin from "../main";

export default class NoteSplitterSettingsTab extends PluginSettingTab {

plugin: NoteSplitterPlugin;

constructor(app: App, plugin: NoteSplitterPlugin) {
Expand All @@ -11,44 +10,52 @@ export default class NoteSplitterSettingsTab extends PluginSettingTab {
}

display() {
let { containerEl } = this;
const { containerEl } = this;

containerEl.empty();

new Setting(containerEl)
.setName("Folder path")
.setDesc("The path to the folder that split notes will be placed in")
.setDesc(
"The path to the folder that split notes will be placed in. If left empty, the folder of the original note will be used.",
)
.addText((text) =>
text
.setValue(this.plugin.settings.saveFolderPath)
.onChange(async (value) => {
this.plugin.settings.saveFolderPath = value;
await this.plugin.saveSettings();
})
text.setValue(this.plugin.settings.saveFolderPath).onChange(async (value) => {
this.plugin.settings.saveFolderPath = value;
await this.plugin.saveSettings();
}),
);

new Setting(containerEl)
.setName("delimiter")
.setDesc("The delimiter to split by")
.setName("Delimiter")
.setDesc("The delimiter to split by.")
.addText((text) =>
text
.setValue(this.plugin.settings.delimiter)
.onChange(async (value) => {
this.plugin.settings.delimiter = value;
await this.plugin.saveSettings();
})
text.setValue(this.plugin.settings.delimiter).onChange(async (value) => {
this.plugin.settings.delimiter = value;
await this.plugin.saveSettings();
}),
);

new Setting(containerEl)
.setName("Use content as title")
.setDesc("If true, the first sentence will be used as the title of the note, otherwise a timestamp will be used e.g. note-splitter-1702591910")
.setDesc(
"If true, the first sentence will be used as the title of the note, otherwise a timestamp will be used e.g. note-splitter-1702591910.",
)
.addToggle((text) =>
text.setValue(this.plugin.settings.useContentAsTitle).onChange(async (value) => {
this.plugin.settings.useContentAsTitle = value;
await this.plugin.saveSettings();
}),
);

new Setting(containerEl)
.setName("Delete original")
.setDesc("Delete the original note after a successful split.")
.addToggle((text) =>
text
.setValue(this.plugin.settings.useContentAsTitle)
.onChange(async (value) => {
this.plugin.settings.useContentAsTitle = value;
await this.plugin.saveSettings();
})
text.setValue(this.plugin.settings.deleteOriginalNote).onChange(async (value) => {
this.plugin.settings.deleteOriginalNote = value;
await this.plugin.saveSettings();
}),
);
}
}
8 changes: 4 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export const findFrontmatterEndIndex = (value: string) => {
}
// If no match is found, return -1
return -1;
}
};

/**
* Escapes characters invalid for a file name
* @param value
* @param value
*/
export const escapeInvalidFileNameChars = (value: string) => {
// Replace colon with hyphen
Expand All @@ -36,12 +36,12 @@ export const escapeInvalidFileNameChars = (value: string) => {
// Replace pipe with nothing
value = value.replace(/\|/g, "");
return value;
}
};

/**
* Trims the string to the maximum length allowed for a file name
*/
export const trimForFileName = (value: string, extension: string) => {
const MAX_LENGTH_MAC_OS = 255;
return value.substring(0, MAX_LENGTH_MAC_OS - extension.length - 1);
}
};
37 changes: 15 additions & 22 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": ["DOM", "ES5", "ES6", "ES7"]
},
"include": ["**/*.ts"]
}

0 comments on commit d36eb15

Please sign in to comment.