343 lines
8.5 KiB
TypeScript
343 lines
8.5 KiB
TypeScript
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";
|
|
|
|
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",
|
|
};
|
|
|
|
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;
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
)}`;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.scrapsDirectory}/${this.nextScrapFileName}.md`,
|
|
""
|
|
);
|
|
|
|
await this.app.workspace
|
|
.getLeaf(this.newScrapLeafType)
|
|
.openFile(newScrap);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
new Notice("No file is currently open");
|
|
return;
|
|
}
|
|
|
|
await this.ensureScrapDir();
|
|
|
|
const renamePath = `${this.scrapsDirectory}/${currentFile.name}`;
|
|
|
|
await this.app.fileManager.renameFile(currentFile, renamePath);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
new Notice("No file is currently open");
|
|
return;
|
|
}
|
|
|
|
await this.ensureScrapDir();
|
|
|
|
const newScrap = await this.app.vault.copy(
|
|
currentFile,
|
|
`${this.scrapsDirectory}/${currentFile.name}`
|
|
);
|
|
|
|
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({
|
|
...COMMANDS.New,
|
|
callback: async () => {
|
|
this.createNewMarkdownScrap();
|
|
},
|
|
});
|
|
|
|
this.addRibbonIcon(COMMANDS.New.icon, COMMANDS.New.name, async () => {
|
|
this.createNewMarkdownScrap();
|
|
});
|
|
|
|
/////////////////////////////
|
|
// Command: Move to Scraps //
|
|
/////////////////////////////
|
|
this.addCommand({
|
|
...COMMANDS.Move,
|
|
editorCallback: async () => this.moveCurrentFileToScraps(),
|
|
});
|
|
|
|
this.addRibbonIcon(COMMANDS.Move.icon, COMMANDS.Move.name, async () => {
|
|
this.moveCurrentFileToScraps();
|
|
});
|
|
|
|
/////////////////////////////
|
|
// Command: Copy to Scraps //
|
|
/////////////////////////////
|
|
this.addCommand({
|
|
...COMMANDS.Copy,
|
|
editorCallback: () => this.copyCurrentFileToScraps(),
|
|
});
|
|
|
|
this.addRibbonIcon(COMMANDS.Copy.icon, COMMANDS.Copy.name, async () => {
|
|
this.copyCurrentFileToScraps();
|
|
});
|
|
|
|
///////////////////////////////
|
|
// Command: Rename to Scraps //
|
|
///////////////////////////////
|
|
this.addCommand({
|
|
...COMMANDS.Rename,
|
|
editorCallback: () => this.copyCurrentFileToScraps(),
|
|
});
|
|
|
|
this.addRibbonIcon(
|
|
COMMANDS.Rename.icon,
|
|
COMMANDS.Rename.name,
|
|
async () => {
|
|
this.renameCurrentFileAsScrap();
|
|
}
|
|
);
|
|
}
|
|
|
|
onunload() {}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign(
|
|
{},
|
|
DEFAULT_SETTINGS,
|
|
await this.loadData()
|
|
);
|
|
}
|
|
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
}
|
|
}
|
|
|
|
class ScrapsSettingTab extends PluginSettingTab {
|
|
plugin: ScrapsPlugin;
|
|
|
|
constructor(app: App, plugin: ScrapsPlugin) {
|
|
super(app, plugin);
|
|
this.plugin = plugin;
|
|
}
|
|
|
|
display(): void {
|
|
const { containerEl } = this;
|
|
|
|
containerEl.empty();
|
|
|
|
new Setting(containerEl)
|
|
.setName("Scraps Root Directory")
|
|
.setDesc("The directory where all your scraps will be stored")
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("Enter directory")
|
|
.setValue(this.plugin.settings.scrapsRootDir)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.scrapsRootDir = value;
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
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(
|
|
`Preview: ${format(this.plugin.settings.scrapsPathFormat)}`
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("Enter format")
|
|
.setValue(this.plugin.settings.scrapsPathFormat)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.scrapsPathFormat = value;
|
|
pathFormatSetting.setDesc(`Preview: ${format(value)}`);
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
const fileFormatSetting = new Setting(containerEl)
|
|
.setName("Scraps File Name Format")
|
|
.setDesc(
|
|
`Preview: ${
|
|
format(this.plugin.settings.scrapsFileName) + ".md"
|
|
}`
|
|
)
|
|
.addText((text) =>
|
|
text
|
|
.setPlaceholder("Enter format")
|
|
.setValue(this.plugin.settings.scrapsFileName)
|
|
.onChange(async (value) => {
|
|
this.plugin.settings.scrapsFileName = value;
|
|
fileFormatSetting.setDesc(
|
|
`Preview: ${format(value) + ".md"}`
|
|
);
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
|
|
new Setting(containerEl)
|
|
.setName("Formatting Help")
|
|
.setDesc("Tokens: {time <moment format>}, {A}, {N}, {MW}");
|
|
}
|
|
}
|