mirror of
https://github.com/binxly/Atomizer.git
synced 2026-07-22 05:31:35 +00:00
Initial commit
This commit is contained in:
parent
634c0da550
commit
94cbd32e43
5 changed files with 2733 additions and 206 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 contact@binx.page
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
104
README.md
104
README.md
|
|
@ -1,94 +1,18 @@
|
|||
# Obsidian Sample Plugin
|
||||
# Atomic Notes Generator for Obsidian
|
||||
|
||||
This is a sample plugin for Obsidian (https://obsidian.md).
|
||||
This plugin helps break down large notes into smaller, atomic notes using OpenAI's API.
|
||||
|
||||
This project uses TypeScript to provide type checking and documentation.
|
||||
The repo depends on the latest plugin API (obsidian.d.ts) in TypeScript Definition format, which contains TSDoc comments describing what it does.
|
||||
## Network Usage Disclosure
|
||||
This plugin makes network requests to:
|
||||
- OpenAI API (api.openai.com) for generating atomic notes from your content
|
||||
- Your notes' content is sent to OpenAI for processing. Please review OpenAI's privacy policy.
|
||||
|
||||
This sample plugin demonstrates some of the basic functionality the plugin API can do.
|
||||
- Adds a ribbon icon, which shows a Notice when clicked.
|
||||
- Adds a command "Open Sample Modal" which opens a Modal.
|
||||
- Adds a plugin setting tab to the settings page.
|
||||
- Registers a global click event and output 'click' to the console.
|
||||
- Registers a global interval which logs 'setInterval' to the console.
|
||||
## Requirements
|
||||
- An OpenAI API key is required for full functionality
|
||||
|
||||
## First time developing plugins?
|
||||
|
||||
Quick starting guide for new plugin devs:
|
||||
|
||||
- Check if [someone already developed a plugin for what you want](https://obsidian.md/plugins)! There might be an existing plugin similar enough that you can partner up with.
|
||||
- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it).
|
||||
- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder.
|
||||
- Install NodeJS, then run `npm i` in the command line under your repo folder.
|
||||
- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`.
|
||||
- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`.
|
||||
- Reload Obsidian to load the new version of your plugin.
|
||||
- Enable plugin in settings window.
|
||||
- For updates to the Obsidian API run `npm update` in the command line under your repo folder.
|
||||
|
||||
## Releasing new releases
|
||||
|
||||
- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release.
|
||||
- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible.
|
||||
- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases
|
||||
- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release.
|
||||
- Publish the release.
|
||||
|
||||
> You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`.
|
||||
> The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json`
|
||||
|
||||
## Adding your plugin to the community plugin list
|
||||
|
||||
- Check the [plugin guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines).
|
||||
- Publish an initial version.
|
||||
- Make sure you have a `README.md` file in the root of your repo.
|
||||
- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin.
|
||||
|
||||
## How to use
|
||||
|
||||
- Clone this repo.
|
||||
- Make sure your NodeJS is at least v16 (`node --version`).
|
||||
- `npm i` or `yarn` to install dependencies.
|
||||
- `npm run dev` to start compilation in watch mode.
|
||||
|
||||
## Manually installing the plugin
|
||||
|
||||
- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`.
|
||||
|
||||
## Improve code quality with eslint (optional)
|
||||
- [ESLint](https://eslint.org/) is a tool that analyzes your code to quickly find problems. You can run ESLint against your plugin to find common bugs and ways to improve your code.
|
||||
- To use eslint with this project, make sure to install eslint from terminal:
|
||||
- `npm install -g eslint`
|
||||
- To use eslint to analyze this project use this command:
|
||||
- `eslint main.ts`
|
||||
- eslint will then create a report with suggestions for code improvement by file and line number.
|
||||
- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder:
|
||||
- `eslint .\src\`
|
||||
|
||||
## Funding URL
|
||||
|
||||
You can include funding URLs where people who use your plugin can financially support it.
|
||||
|
||||
The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": "https://buymeacoffee.com"
|
||||
}
|
||||
```
|
||||
|
||||
If you have multiple URLs, you can also do:
|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": {
|
||||
"Buy Me a Coffee": "https://buymeacoffee.com",
|
||||
"GitHub Sponsor": "https://github.com/sponsors",
|
||||
"Patreon": "https://www.patreon.com/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Documentation
|
||||
|
||||
See https://github.com/obsidianmd/obsidian-api
|
||||
## Functionality
|
||||
- Set your OpenAI API key in settings.
|
||||
- Press `Alt+A` to open the command palette and select "Atomize Selected Note" to process the currently selected note. Alternatively, you can use the icon in the left sidebar.
|
||||
- Send selected note to OpenAI for processing, where it will be broken down into atomic notes that aim to extract key insights from the original note.
|
||||
- Frontmatter will be added to each new note, including the date and any custom tags you've configured.
|
||||
- Models supported: `gpt-4o-mini`, `gpt-4o`; Can be configured in settings.
|
||||
|
|
|
|||
417
main.ts
417
main.ts
|
|
@ -1,134 +1,331 @@
|
|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
import OpenAI from 'openai';
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
// Types and Interfaces
|
||||
interface AtomicPluginSettings {
|
||||
apiKey: string;
|
||||
outputFolder: string;
|
||||
model: string;
|
||||
enableAtomizedTag: boolean;
|
||||
customTags: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
mySetting: 'default'
|
||||
// Constants
|
||||
const DEFAULT_SETTINGS: AtomicPluginSettings = {
|
||||
apiKey: '',
|
||||
outputFolder: 'atomic-notes',
|
||||
model: 'gpt-4o-mini',
|
||||
enableAtomizedTag: true,
|
||||
customTags: ''
|
||||
}
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
const getCurrentDateTime = (): string => {
|
||||
const date = new Date();
|
||||
return date.toISOString()
|
||||
.replace('T', ' ')
|
||||
.replace(/\.\d+Z$/, '');
|
||||
};
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
// Move SYSTEM_PROMPT into a function that takes settings as a parameter
|
||||
const getSystemPrompt = (settings: AtomicPluginSettings): string => {
|
||||
return `You are an expert at creating atomic notes from a single, larger note.
|
||||
Take the content from a larger note and break it down into separate compact yet detailed atomic notes. Each note MUST be separated by placing '<<<>>>' on its own line between notes. Do not include an index or main note. Follow these rules:
|
||||
1. Each note should contain exactly one clear idea. This can contain multiple lines.
|
||||
2. Each note must have a YAML frontmatter section at the top with:
|
||||
---
|
||||
date: "${getCurrentDateTime()}"
|
||||
tags: ${settings.enableAtomizedTag ? 'atomized' : ''}${settings.customTags ? (settings.enableAtomizedTag ? ', ' : '') + settings.customTags : ''}
|
||||
---
|
||||
3. You MUST separate each note by placing '<<<>>>' on its own line between notes
|
||||
4. After the frontmatter, each note must start with a level 1 heading (# Title)
|
||||
5. The content should be self-contained and independently understandable
|
||||
6. Use proper Markdown formatting
|
||||
7. Do not include the separator at the start or end of the response`;
|
||||
};
|
||||
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
|
||||
// Called when the user clicks the icon.
|
||||
new Notice('This is a notice!');
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
// OpenAI Service
|
||||
class OpenAIService {
|
||||
constructor(private apiKey: string, private model: string, private settings: AtomicPluginSettings) {}
|
||||
|
||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||
const statusBarItemEl = this.addStatusBarItem();
|
||||
statusBarItemEl.setText('Status Bar Text');
|
||||
async generateAtomicNotes(content: string, dateTime: string): Promise<string> {
|
||||
if (!this.apiKey) {
|
||||
new Notice('OpenAI API key is not set. Please configure it in plugin settings.');
|
||||
return '';
|
||||
}
|
||||
|
||||
// This adds a simple command that can be triggered anywhere
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-simple',
|
||||
name: 'Open sample modal (simple)',
|
||||
callback: () => {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
});
|
||||
// This adds an editor command that can perform some operation on the current editor instance
|
||||
this.addCommand({
|
||||
id: 'sample-editor-command',
|
||||
name: 'Sample editor command',
|
||||
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||
console.log(editor.getSelection());
|
||||
editor.replaceSelection('Sample Editor Command');
|
||||
}
|
||||
});
|
||||
// This adds a complex command that can check whether the current state of the app allows execution of the command
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-complex',
|
||||
name: 'Open sample modal (complex)',
|
||||
checkCallback: (checking: boolean) => {
|
||||
// Conditions to check
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (markdownView) {
|
||||
// If checking is true, we're simply "checking" if the command can be run.
|
||||
// If checking is false, then we want to actually perform the operation.
|
||||
if (!checking) {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
// Inform user about network request
|
||||
new Notice('Sending request to OpenAI...', 3000);
|
||||
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
const openai = new OpenAI({
|
||||
apiKey: this.apiKey,
|
||||
dangerouslyAllowBrowser: true
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: this.model,
|
||||
messages: [{
|
||||
role: "system",
|
||||
content: getSystemPrompt(this.settings)
|
||||
}, {
|
||||
role: "user",
|
||||
content: content
|
||||
}],
|
||||
temperature: 0.7,
|
||||
max_tokens: 4000
|
||||
});
|
||||
|
||||
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
|
||||
// Using this function will automatically remove the event listener when this plugin is disabled.
|
||||
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
||||
console.log('click', evt);
|
||||
});
|
||||
|
||||
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
|
||||
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
return completion.choices[0]?.message?.content ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
class SampleModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
}
|
||||
// Notes Manager
|
||||
class NotesManager {
|
||||
private usedTitles = new Set<string>();
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.setText('Woah!');
|
||||
}
|
||||
constructor(private app: App, private folderPath: string) {}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
async createFolder(): Promise<void> {
|
||||
if (!(await this.app.vault.adapter.exists(this.folderPath))) {
|
||||
await this.app.vault.createFolder(this.folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
async saveNote(note: string): Promise<void> {
|
||||
const titleMatch = note.match(/^#\s+(.+)$/m);
|
||||
if (!titleMatch) {
|
||||
console.warn('No title found in note:', note.slice(0, 100));
|
||||
return;
|
||||
}
|
||||
|
||||
const title = this.getUniqueTitle(titleMatch[1].trim());
|
||||
const fileName = `${this.folderPath}/${this.sanitizeTitle(title)}.md`;
|
||||
await this.app.vault.create(fileName, note.trim());
|
||||
}
|
||||
|
||||
private getUniqueTitle(baseTitle: string): string {
|
||||
let title = baseTitle;
|
||||
let counter = 1;
|
||||
while (this.usedTitles.has(title)) {
|
||||
title = `${baseTitle} ${counter}`;
|
||||
counter++;
|
||||
}
|
||||
this.usedTitles.add(title);
|
||||
return title;
|
||||
}
|
||||
|
||||
private sanitizeTitle(title: string): string {
|
||||
return title.replace(/[\\/:*?"<>|]/g, '-');
|
||||
}
|
||||
}
|
||||
|
||||
class SampleSettingTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
// Main Plugin Class
|
||||
export default class AtomicNotesPlugin extends Plugin {
|
||||
settings: AtomicPluginSettings;
|
||||
private statusBarItem: HTMLElement;
|
||||
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
// Add ribbon icon
|
||||
this.addRibbonIcon('atom', 'Atomize Note', async () => {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView) {
|
||||
await this.atomizeNote(activeView);
|
||||
} else {
|
||||
new Notice('Please open a note to atomize');
|
||||
}
|
||||
});
|
||||
|
||||
containerEl.empty();
|
||||
// Add command
|
||||
this.addCommand({
|
||||
id: 'atomize-current-note',
|
||||
name: 'Atomize current note',
|
||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
||||
await this.atomizeNote(view);
|
||||
}
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Setting #1')
|
||||
.setDesc('It\'s a secret')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your secret')
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.mySetting = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
// Add settings tab
|
||||
this.addSettingTab(new AtomicSettingTab(this.app, this));
|
||||
|
||||
this.statusBarItem = this.addStatusBarItem();
|
||||
this.statusBarItem.setText('Atomic Notes: Ready');
|
||||
}
|
||||
|
||||
async atomizeNote(view: MarkdownView) {
|
||||
if (!this.validateSettings()) return;
|
||||
|
||||
const content = view.getViewData();
|
||||
if (!content.trim()) {
|
||||
new Notice('The current note is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
new ConfirmationModal(this.app, async () => {
|
||||
const loadingNotice = new Notice('Processing note...', 0);
|
||||
|
||||
try {
|
||||
const openAIService = new OpenAIService(
|
||||
this.settings.apiKey,
|
||||
this.settings.model,
|
||||
this.settings
|
||||
);
|
||||
const notesManager = new NotesManager(this.app, this.settings.outputFolder);
|
||||
|
||||
const responseContent = await openAIService.generateAtomicNotes(content, this.getCurrentDateTime());
|
||||
if (!responseContent) throw new Error('No content received from OpenAI');
|
||||
|
||||
const atomicNotes = responseContent
|
||||
.split('<<<>>>')
|
||||
.map(note => note.trim())
|
||||
.filter(note => note.length > 0);
|
||||
|
||||
if (atomicNotes.length === 0) {
|
||||
throw new Error('No atomic notes were generated. The response may not be properly formatted.');
|
||||
}
|
||||
|
||||
await notesManager.createFolder();
|
||||
|
||||
for (const note of atomicNotes) {
|
||||
await notesManager.saveNote(note);
|
||||
}
|
||||
|
||||
new Notice(`Successfully created ${atomicNotes.length} atomic notes`);
|
||||
} catch (error) {
|
||||
this.handleError(error);
|
||||
} finally {
|
||||
loadingNotice.hide();
|
||||
this.statusBarItem.setText('Atomic Notes: Ready');
|
||||
}
|
||||
}).open();
|
||||
}
|
||||
|
||||
private validateSettings(): boolean {
|
||||
if (!this.settings.apiKey) {
|
||||
new Notice('Please set your OpenAI API key in settings');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
console.error('Detailed error:', error);
|
||||
new Notice(`Error: ${error.message || 'Unknown error occurred'}`);
|
||||
|
||||
if (error.response) {
|
||||
console.error('OpenAI API Error:', {
|
||||
status: error.response.status,
|
||||
data: error.response.data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
private getCurrentDateTime(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
class AtomicSettingTab extends PluginSettingTab {
|
||||
plugin: AtomicNotesPlugin;
|
||||
|
||||
constructor(app: App, plugin: AtomicNotesPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('OpenAI API Key')
|
||||
.setDesc('Enter your OpenAI API key')
|
||||
.addText(text => text
|
||||
.setPlaceholder('sk-...')
|
||||
.setValue(this.plugin.settings.apiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.apiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Model')
|
||||
.setDesc('Choose the OpenAI model to use')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('gpt-4o-mini', 'GPT-4o Mini')
|
||||
.addOption('gpt-4o', 'GPT-4o')
|
||||
.setValue(this.plugin.settings.model)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.model = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Output Folder')
|
||||
.setDesc('Folder where atomic notes will be created')
|
||||
.addText(text => text
|
||||
.setPlaceholder('atomic-notes')
|
||||
.setValue(this.plugin.settings.outputFolder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.outputFolder = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Enable "atomized" Tag')
|
||||
.setDesc('Automatically add the "atomized" tag to generated notes')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.enableAtomizedTag)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enableAtomizedTag = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Custom Tags')
|
||||
.setDesc('Additional tags to add to generated notes (comma-separated)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('tag1, tag2, tag3')
|
||||
.setValue(this.plugin.settings.customTags)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.customTags = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class ConfirmationModal extends Modal {
|
||||
constructor(app: App, private onConfirm: () => void) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.setText('This will create multiple atomic notes in your vault. Continue?');
|
||||
|
||||
new Setting(contentEl)
|
||||
.addButton(btn => btn
|
||||
.setButtonText('Cancel')
|
||||
.onClick(() => this.close()))
|
||||
.addButton(btn => btn
|
||||
.setButtonText('Continue')
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.close();
|
||||
this.onConfirm();
|
||||
}));
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"id": "sample-plugin",
|
||||
"name": "Sample Plugin",
|
||||
"id": "atomic-notes-generator",
|
||||
"name": "Atomizer",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Demonstrates some of the capabilities of the Obsidian API.",
|
||||
"author": "Obsidian",
|
||||
"authorUrl": "https://obsidian.md",
|
||||
"fundingUrl": "https://obsidian.md/pricing",
|
||||
"description": "Turn any text into insightful atomic notes.",
|
||||
"author": "Zac Bagley",
|
||||
"authorUrl": "https://binx.page",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
2386
package-lock.json
generated
Normal file
2386
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue