Build out lib dir
This commit is contained in:
parent
b93e472f8d
commit
dea972cfda
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -20,3 +20,5 @@ data.json
|
|||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
dist
|
|
@ -6,9 +6,10 @@ This plugin manages a folder in your Obsidian Vault which contains "Scraps": fil
|
|||
|
||||
## Commands
|
||||
|
||||
- `Scraps: Create new Scrap` - Creates a new file based on the plugin settings
|
||||
- `Scraps: Convert current file to Scrap` - Moves and renames the current file to be a scrap
|
||||
- `Scraps: Copy current file to Scraps` - Creates a copy of the current file as a scrap
|
||||
- `Scraps: Create new Scrap` - Creates a new file based on the plugin settings.
|
||||
- `Scraps: Move current file to Scraps` - Moves and the current file into the current Scrap directory. Does not rename the file.
|
||||
- `Scraps: Copy current file to Scraps` - Creates a copy of the current file in the current Scrap directory. Does not rename the file.
|
||||
- `Scraps: Rename current file as Scrap` - Renames the current file using the scrap file name settings and moves the file into the current Scrap directory.
|
||||
|
||||
## Status
|
||||
|
||||
|
|
|
@ -1,20 +1,28 @@
|
|||
import { moment } from "obsidian";
|
||||
import { adjectives } from "adjectives.js";
|
||||
import { capitalize, sample } from "lodash-es";
|
||||
import { nouns } from "nouns.js";
|
||||
import { adjectives } from "./resource/adjectives.js";
|
||||
import { nouns } from "./resource/nouns.js";
|
||||
|
||||
interface FormatOptions {
|
||||
ext?: string;
|
||||
}
|
||||
/*
|
||||
* A simple formatter/templater which uses a {tag} format for tokens.
|
||||
*
|
||||
* Supported tokens:
|
||||
* - {N}: Random noun
|
||||
* - {n}: Random noun (lower case)
|
||||
* - {A}: Random adjective
|
||||
* - {a}: Random adjective (lower case)
|
||||
* - {MW}: Month week (1-5)
|
||||
* - {time <moment format>}: Current date/time in the specified moment format
|
||||
*
|
||||
* Examples:
|
||||
* format("Note {N} created at {time HH:mm}") -> "Note Apple created at 14:30"
|
||||
* format("Note {n} created at {time HH:mm}") -> "Note apple created at 14:30"
|
||||
* format("Scrap from {time YYYY-MM-DD}") -> "Scrap from 2024-06-01"
|
||||
*/
|
||||
|
||||
export function format(str: string, options?: FormatOptions): string {
|
||||
export function format(str: string): string {
|
||||
let formatted = str.trim();
|
||||
|
||||
let extension = options?.ext ?? "";
|
||||
if (extension.length > 0 && !extension.startsWith(".")) {
|
||||
extension = `.${extension}`;
|
||||
}
|
||||
|
||||
// Replace {N} with a random noun
|
||||
formatted = formatted.replace(/{N}/g, () => {
|
||||
return capitalize(sample(nouns));
|
||||
|
@ -47,5 +55,5 @@ export function format(str: string, options?: FormatOptions): string {
|
|||
}
|
||||
);
|
||||
|
||||
return formatted + extension;
|
||||
return formatted;
|
||||
}
|
6
lib/resource/icons.ts
Normal file
6
lib/resource/icons.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export const ICON = {
|
||||
New: "file-plus",
|
||||
Rename: "pen-line",
|
||||
Move: "replace",
|
||||
Copy: "copy-plus",
|
||||
} as const;
|
27
lib/util.ts
Normal file
27
lib/util.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { Vault } from "obsidian";
|
||||
|
||||
export async function mkdirp(vault: Vault, folderPath: string): Promise<void> {
|
||||
// Noop if the path exists
|
||||
if (vault.getAbstractFileByPath(folderPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start building the path incrementally
|
||||
const pathParts = folderPath.split("/");
|
||||
let currentPath = "";
|
||||
|
||||
for (const part of pathParts) {
|
||||
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
||||
|
||||
try {
|
||||
// Check if folder exists
|
||||
if (!vault.getAbstractFileByPath(currentPath)) {
|
||||
// Create the folder if it doesn't exist
|
||||
await vault.createFolder(currentPath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error creating folder ${currentPath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
261
main.ts
261
main.ts
|
@ -1,90 +1,125 @@
|
|||
import {
|
||||
App,
|
||||
Plugin,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
Vault,
|
||||
Notice,
|
||||
} from "obsidian";
|
||||
import { format } from "./format.js";
|
||||
import { App, Plugin, PluginSettingTab, Setting, Notice } from "obsidian";
|
||||
import { format } from "./lib/format.js";
|
||||
import { mkdirp } from "./lib/util.js";
|
||||
import { ICON } from "./lib/resource/icons.js";
|
||||
|
||||
interface ScrapsPluginSettings {
|
||||
type NewScrapBehaviorOption = "default" | "tab" | "split" | "window";
|
||||
|
||||
export interface ScrapsPluginSettings {
|
||||
scrapsRootDir: string;
|
||||
scrapsPathFormat: string;
|
||||
scrapsFileName: string;
|
||||
newScrapBehavior: NewScrapBehaviorOption;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: ScrapsPluginSettings = {
|
||||
scrapsRootDir: "_Scraps",
|
||||
scrapsPathFormat: "{time YYYY} Week {time WW MMM}",
|
||||
scrapsFileName: "{time DDDD} {A} {N}",
|
||||
newScrapBehavior: "default",
|
||||
};
|
||||
|
||||
const ICON = {
|
||||
New: "file-plus",
|
||||
Convert: "shuffle",
|
||||
Move: "replace",
|
||||
Copy: "copy-plus",
|
||||
} as const;
|
||||
|
||||
async function mkdirp(vault: Vault, folderPath: string): Promise<void> {
|
||||
const pathParts = folderPath.split("/");
|
||||
|
||||
// Start building the path incrementally
|
||||
let currentPath = "";
|
||||
|
||||
for (const part of pathParts) {
|
||||
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
||||
|
||||
try {
|
||||
// Check if folder exists
|
||||
if (!vault.getAbstractFileByPath(currentPath)) {
|
||||
// Create the folder if it doesn't exist
|
||||
await vault.createFolder(currentPath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error creating folder ${currentPath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
type CommandId = "New" | "Move" | "Copy" | "Rename";
|
||||
interface CommandDef {
|
||||
id: string;
|
||||
icon: string;
|
||||
name: string;
|
||||
}
|
||||
const COMMANDS: Record<CommandId, CommandDef> = {
|
||||
New: {
|
||||
id: "scraps-new",
|
||||
icon: ICON.New,
|
||||
name: "Scraps: Create new Scrap",
|
||||
},
|
||||
Move: {
|
||||
id: "scraps-move",
|
||||
icon: ICON.Move,
|
||||
name: "Scraps: Move current file to Scraps",
|
||||
},
|
||||
Copy: {
|
||||
id: "scraps-copy",
|
||||
icon: ICON.Copy,
|
||||
name: "Scraps: Copy current file to Scraps",
|
||||
},
|
||||
Rename: {
|
||||
id: "scraps-rename",
|
||||
icon: ICON.Rename,
|
||||
name: "Scraps: Rename current file as Scrap",
|
||||
},
|
||||
};
|
||||
|
||||
export default class ScrapsPlugin extends Plugin {
|
||||
settings: ScrapsPluginSettings;
|
||||
|
||||
async ensureScrapDir(): Promise<string> {
|
||||
const pathname = `${this.getScrapDir()}`;
|
||||
|
||||
await mkdirp(this.app.vault, pathname);
|
||||
|
||||
return pathname;
|
||||
/**
|
||||
* Ensures that the scrap directory exists by creating it if it does not
|
||||
* already exist.
|
||||
*
|
||||
* @returns {Promise<void>} A promise that resolves when the directory
|
||||
* has been ensured.
|
||||
*/
|
||||
async ensureScrapDir(): Promise<void> {
|
||||
await mkdirp(this.app.vault, this.scrapsDirectory);
|
||||
}
|
||||
|
||||
getScrapDir(): string {
|
||||
/**
|
||||
* The current scrap directory path based on the configured root
|
||||
* directory and path format.
|
||||
*
|
||||
* @returns {string} The full path to the current scrap directory.
|
||||
*/
|
||||
get scrapsDirectory(): string {
|
||||
return `${this.settings.scrapsRootDir}/${format(
|
||||
this.settings.scrapsPathFormat
|
||||
)}`;
|
||||
}
|
||||
|
||||
getScrapFileName(): string {
|
||||
return format(this.settings.scrapsFileName, { ext: "md" });
|
||||
/**
|
||||
* A formatted scrap file name based on the current settings.
|
||||
*
|
||||
* @note This will generate a new name each time it is accessed.
|
||||
* @returns {string} The formatted scrap file name.
|
||||
*/
|
||||
get nextScrapFileName(): string {
|
||||
return format(this.settings.scrapsFileName);
|
||||
}
|
||||
|
||||
getScrapFilePath(): string {
|
||||
return `${this.getScrapDir()}/${this.getScrapFileName()}`;
|
||||
get newScrapLeafType(): false | "tab" | "split" | "window" {
|
||||
switch (this.settings.newScrapBehavior) {
|
||||
case "tab":
|
||||
return "tab";
|
||||
case "split":
|
||||
return "split";
|
||||
case "window":
|
||||
return "window";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async createScrap() {
|
||||
/**
|
||||
* Creates a new scrap file in the designated scrap directory.
|
||||
*
|
||||
* @returns {Promise<void>} A promise that resolves when the scrap file is created and opened.
|
||||
*/
|
||||
async createNewMarkdownScrap(): Promise<void> {
|
||||
await this.ensureScrapDir();
|
||||
const newScrap = await this.app.vault.create(
|
||||
this.getScrapFilePath(),
|
||||
`${this.scrapsDirectory}/${this.nextScrapFileName}.md`,
|
||||
""
|
||||
);
|
||||
|
||||
await this.app.workspace.getLeaf(false).openFile(newScrap);
|
||||
await this.app.workspace
|
||||
.getLeaf(this.newScrapLeafType)
|
||||
.openFile(newScrap);
|
||||
}
|
||||
|
||||
async convertToScrap(rename = true) {
|
||||
/**
|
||||
* Moves the currently active file to the Scraps directory.
|
||||
*
|
||||
* @returns {Promise<void>} A promise that resolves when the file has been converted to a scrap.
|
||||
*/
|
||||
async moveCurrentFileToScraps(): Promise<void> {
|
||||
const currentFile = this.app.workspace.getActiveFile();
|
||||
|
||||
if (currentFile === null) {
|
||||
|
@ -94,13 +129,38 @@ export default class ScrapsPlugin extends Plugin {
|
|||
|
||||
await this.ensureScrapDir();
|
||||
|
||||
const filename = rename ? this.getScrapFileName() : currentFile.name;
|
||||
const renamePath = `${this.getScrapDir()}/${filename}`;
|
||||
const renamePath = `${this.scrapsDirectory}/${currentFile.name}`;
|
||||
|
||||
await this.app.fileManager.renameFile(currentFile, renamePath);
|
||||
}
|
||||
|
||||
async copyToScrap() {
|
||||
/**
|
||||
* Renames the currently active file to use a generated Scrap title
|
||||
*
|
||||
* @returns {Promise<void>} A promise that resolves when the file has been renamed.
|
||||
*/
|
||||
async renameCurrentFileAsScrap(): Promise<void> {
|
||||
const currentFile = this.app.workspace.getActiveFile();
|
||||
|
||||
if (currentFile === null) {
|
||||
new Notice("No file is currently open");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.ensureScrapDir();
|
||||
|
||||
const renamePath = `${this.scrapsDirectory}/${this.nextScrapFileName}.${currentFile.extension}`;
|
||||
|
||||
await this.app.fileManager.renameFile(currentFile, renamePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the currently active file to a designated "scrap" directory and opens the copied file.
|
||||
* If no file is currently open, a notice is displayed to the user.
|
||||
*
|
||||
* @returns {Promise<void>} A promise that resolves when the file has been copied and opened.
|
||||
*/
|
||||
async copyCurrentFileToScraps(): Promise<void> {
|
||||
const currentFile = this.app.workspace.getActiveFile();
|
||||
|
||||
if (currentFile === null) {
|
||||
|
@ -112,61 +172,71 @@ export default class ScrapsPlugin extends Plugin {
|
|||
|
||||
const newScrap = await this.app.vault.copy(
|
||||
currentFile,
|
||||
this.getScrapFilePath()
|
||||
`${this.scrapsDirectory}/${currentFile.name}`
|
||||
);
|
||||
|
||||
await this.app.workspace.getLeaf(false).openFile(newScrap);
|
||||
await this.app.workspace
|
||||
.getLeaf(this.newScrapLeafType)
|
||||
.openFile(newScrap);
|
||||
}
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.addSettingTab(new ScrapsSettingTab(this.app, this));
|
||||
|
||||
///////////////////////////////
|
||||
// Command: Create New Scrap //
|
||||
///////////////////////////////
|
||||
this.addCommand({
|
||||
id: "scraps-new",
|
||||
name: "Scraps: Create new Scrap",
|
||||
icon: ICON.New,
|
||||
...COMMANDS.New,
|
||||
callback: async () => {
|
||||
this.createScrap();
|
||||
this.createNewMarkdownScrap();
|
||||
},
|
||||
});
|
||||
|
||||
this.addRibbonIcon(ICON.New, "Create new Scrap", async () => {
|
||||
this.createScrap();
|
||||
this.addRibbonIcon(COMMANDS.New.icon, COMMANDS.New.name, async () => {
|
||||
this.createNewMarkdownScrap();
|
||||
});
|
||||
|
||||
/////////////////////////////
|
||||
// Command: Move to Scraps //
|
||||
/////////////////////////////
|
||||
this.addCommand({
|
||||
id: "scraps-convert",
|
||||
name: "Scraps: Convert current file to Scrap",
|
||||
icon: ICON.Convert,
|
||||
editorCallback: async () => this.convertToScrap(),
|
||||
...COMMANDS.Move,
|
||||
editorCallback: async () => this.moveCurrentFileToScraps(),
|
||||
});
|
||||
|
||||
this.addRibbonIcon(ICON.Convert, "Convert file to Scrap", async () => {
|
||||
this.convertToScrap();
|
||||
this.addRibbonIcon(COMMANDS.Move.icon, COMMANDS.Move.name, async () => {
|
||||
this.moveCurrentFileToScraps();
|
||||
});
|
||||
|
||||
/////////////////////////////
|
||||
// Command: Copy to Scraps //
|
||||
/////////////////////////////
|
||||
this.addCommand({
|
||||
id: "scraps-move",
|
||||
name: "Scraps: Move current file to Scraps",
|
||||
icon: ICON.Move,
|
||||
editorCallback: async () => this.convertToScrap(false),
|
||||
...COMMANDS.Copy,
|
||||
editorCallback: () => this.copyCurrentFileToScraps(),
|
||||
});
|
||||
|
||||
this.addRibbonIcon(ICON.Move, "Move file to Scraps", async () => {
|
||||
this.convertToScrap(false);
|
||||
this.addRibbonIcon(COMMANDS.Copy.icon, COMMANDS.Copy.name, async () => {
|
||||
this.copyCurrentFileToScraps();
|
||||
});
|
||||
|
||||
///////////////////////////////
|
||||
// Command: Rename to Scraps //
|
||||
///////////////////////////////
|
||||
this.addCommand({
|
||||
id: "scraps-copy",
|
||||
name: "Scraps: Copy current file to Scraps",
|
||||
icon: ICON.Copy,
|
||||
editorCallback: () => this.copyToScrap(),
|
||||
...COMMANDS.Rename,
|
||||
editorCallback: () => this.copyCurrentFileToScraps(),
|
||||
});
|
||||
|
||||
this.addRibbonIcon(ICON.Copy, "Copy file to Scraps", async () => {
|
||||
this.copyToScrap();
|
||||
});
|
||||
this.addRibbonIcon(
|
||||
COMMANDS.Rename.icon,
|
||||
COMMANDS.Rename.name,
|
||||
async () => {
|
||||
this.renameCurrentFileAsScrap();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
|
@ -210,6 +280,25 @@ class ScrapsSettingTab extends PluginSettingTab {
|
|||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("New Scrap Behavior")
|
||||
.setDesc("The behavior when creating a new scrap")
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
default: "Default",
|
||||
tab: "Open in new tab",
|
||||
split: "Split the current pane",
|
||||
window: "Open in new window",
|
||||
})
|
||||
.setValue(this.plugin.settings.newScrapBehavior)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.newScrapBehavior =
|
||||
value as NewScrapBehaviorOption;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
const pathFormatSetting = new Setting(containerEl)
|
||||
.setName("Scraps Path Format")
|
||||
.setDesc(
|
||||
|
@ -229,9 +318,9 @@ class ScrapsSettingTab extends PluginSettingTab {
|
|||
const fileFormatSetting = new Setting(containerEl)
|
||||
.setName("Scraps File Name Format")
|
||||
.setDesc(
|
||||
`Preview: ${format(this.plugin.settings.scrapsFileName, {
|
||||
ext: "md",
|
||||
})}`
|
||||
`Preview: ${
|
||||
format(this.plugin.settings.scrapsFileName) + ".md"
|
||||
}`
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
|
@ -240,7 +329,7 @@ class ScrapsSettingTab extends PluginSettingTab {
|
|||
.onChange(async (value) => {
|
||||
this.plugin.settings.scrapsFileName = value;
|
||||
fileFormatSetting.setDesc(
|
||||
`Preview: ${format(value, { ext: "md" })}`
|
||||
`Preview: ${format(value) + ".md"}`
|
||||
);
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
|
|
5
pack.sh
Executable file
5
pack.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
rm -rf ./dist
|
||||
mkdir dist
|
||||
cp main.js ./dist
|
||||
cp manifest.json ./dist
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"name": "obsidian-scraps",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"description": "Create and manage scraps of information in your vault.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
|
@ -9,7 +9,7 @@
|
|||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"author": "Endeavorance <hello@endeavorance.camp>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
|
||||
This CSS file will be included with your plugin, and
|
||||
available in the app when your plugin is enabled.
|
||||
|
||||
If your plugin does not need CSS, delete this file.
|
||||
|
||||
*/
|
Loading…
Reference in a new issue