mirror of
https://github.com/benstuart0/on-this-day-i-obsidian.git
synced 2026-07-22 05:38:19 +00:00
Add readme, tweak functionality
This commit is contained in:
parent
10177e2155
commit
e9f8ad5cb0
9 changed files with 224 additions and 229 deletions
125
README.md
125
README.md
|
|
@ -1,94 +1,31 @@
|
|||
# Obsidian Sample Plugin
|
||||
|
||||
This is a sample plugin for Obsidian (https://obsidian.md).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
# on-this-day-i
|
||||
This plugin for Obsidian adds AI commands for daily journals.
|
||||
## Features
|
||||
This plugin comes with two commands.
|
||||
### Add Placeholder at Cursor
|
||||
Adds a custom tag at your cursor location which the generation command looks for and replaces if it is available. The tag can be changed in settings.
|
||||
### Generate Through The Years
|
||||
Uses AI to find Daily Notes from the same date as your current note in years past. Generates a brief summary of this date for each instance of it through the years and places it in your note.
|
||||
## How to Use
|
||||
- Enable the plugin in the settings menu and update the plugin settings accordingly
|
||||
- Open the command palette and search for On This Day I to see the commands.
|
||||
- If you want the output to be placed at a certain location in your note, add a placeholder (default is <!OTDI>, can be changed in settings). Run the `On This Day I: Add Placeholder at Cursor` command if you'd like to automatically add a placeholder to your note at cursor location.
|
||||
- Run the command `On This Day I: Generate Through The Years` to generate your yearly insights.
|
||||
### OpenAI
|
||||
- Must have OpenAI API key. See [platform.openai.com](https://platform.openai.com/). Add this key to plugin settings.
|
||||
- Model can be adjusted. Currently support gpt-3.5-turbo and gpt-4. Older models will be cheaper and often faster, but newer models will have higher quality outputs.
|
||||
### Through the Years Date Requirements
|
||||
- Must be run from a note with a valid Date format. Update the date format in settings to match your daily note format.
|
||||
- Daily notes must all follow the same date format (e.g, MMMM D, YYYY would be something like February 6, 2025)
|
||||
- There must be at least one other daily note from this day in a past year.
|
||||
## How to Install
|
||||
### From within Obsidian
|
||||
You can activate this plugin within Obsidian by doing the following:
|
||||
- Open Settings > Third-party plugin
|
||||
- Make sure Safe mode is **off**
|
||||
- Click Browse community plugins
|
||||
- Search for "On This Day I"
|
||||
- Click Install
|
||||
- Once installed, close the community plugins window and activate the newly installed plugin
|
||||
## Obsidian Mobile
|
||||
When using Obsidian on a mobile device, open the command palette to access the On This Day I plugin commands.
|
||||
92
main.ts
92
main.ts
|
|
@ -1,15 +1,11 @@
|
|||
import { MarkdownView, Notice, Plugin, TFolder } from "obsidian";
|
||||
import OpenAI from "openai";
|
||||
import {
|
||||
OnThisDayPluginSettings,
|
||||
DEFAULT_SETTINGS,
|
||||
} from "src/settings";
|
||||
import { OnThisDayPluginSettings, DEFAULT_SETTINGS } from "src/settings";
|
||||
import OnThisDaySettingTab from "src/OnThisDaySettingTab";
|
||||
import aiPrompt from "src/prompt";
|
||||
import {
|
||||
isValidDateString,
|
||||
parseDateFromString
|
||||
} from 'src/dateUtils'
|
||||
import { sysPrompt, constructPrompt } from "src/prompt";
|
||||
import { isValidDateString, parseDateFromString } from "src/dateUtils";
|
||||
import { getMarkdownFilesInFolder, buildOutputBlock } from "src/markdownUtils";
|
||||
import { threadId } from "worker_threads";
|
||||
|
||||
export default class OnThisDayPlugin extends Plugin {
|
||||
settings: OnThisDayPluginSettings;
|
||||
|
|
@ -20,7 +16,7 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
|
||||
this.addCommand({
|
||||
id: "on-this-day-i:on-this-day-placeholder",
|
||||
name: "Add Placeholder",
|
||||
name: "Add Placeholder at Cursor",
|
||||
callback: async () => {
|
||||
await this.addPlaceholder();
|
||||
},
|
||||
|
|
@ -51,7 +47,7 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
// Main Function: Generate Date Summaries
|
||||
//
|
||||
async generateDateSummaries() {
|
||||
// 1. Ensure the command is run from a file with a valid date as its title.
|
||||
// Ensure the command is run from a file with a valid date as its title.
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (!activeFile) {
|
||||
new Notice("No active file found.");
|
||||
|
|
@ -66,7 +62,7 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
|
||||
const inputDateString = activeFile.basename;
|
||||
|
||||
// 2. Parse the input date to extract month and day (ignoring the year)
|
||||
// Parse the input date to extract month and day (ignoring the year)
|
||||
const parsedDate = parseDateFromString(
|
||||
inputDateString,
|
||||
this.settings.dateFormat
|
||||
|
|
@ -79,7 +75,7 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
const day = parsedDate.getDate();
|
||||
const currentYear = parsedDate.getFullYear();
|
||||
|
||||
// 3. Search through the daily notes folder for matching files
|
||||
// Get the folder object for the daily notes folder.
|
||||
const folder = this.app.vault.getAbstractFileByPath(
|
||||
this.settings.dailyNotesFolder
|
||||
);
|
||||
|
|
@ -87,15 +83,8 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
new Notice("Daily notes folder not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all Markdown files in the daily notes folder
|
||||
const files = this.app.vault
|
||||
.getFiles()
|
||||
.filter(
|
||||
(file) =>
|
||||
file.path.startsWith(this.settings.dailyNotesFolder) &&
|
||||
file.extension === "md"
|
||||
);
|
||||
// Get all files in the folder
|
||||
const files = getMarkdownFilesInFolder(folder);
|
||||
|
||||
// Build a map: year => concatenated content of matching daily notes
|
||||
let yearToContent: Record<string, string> = {};
|
||||
|
|
@ -125,13 +114,12 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
// 4. Construct the API prompt that instructs GPT-4 to produce a JSON summary mapping.
|
||||
const prompt = this.constructPrompt(yearToContent, inputDateString);
|
||||
const prompt = constructPrompt(yearToContent, this.settings.customPrompt);
|
||||
|
||||
// 5. Show a loading indicator (persistent notice) until work is done.
|
||||
// Show a loading indicator (persistent notice) until work is done.
|
||||
const loadingNotice = new Notice("Generating summaries...", 0);
|
||||
|
||||
// 6. Call the OpenAI API once with the entire JSON map.
|
||||
// Call the OpenAI API once with the entire JSON map.
|
||||
let responseJSON: Record<string, string>;
|
||||
try {
|
||||
responseJSON = await this.callOpenAI(prompt);
|
||||
|
|
@ -142,12 +130,17 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
// 7. Build the output markdown block.
|
||||
const outputBlock = this.buildOutputBlock(responseJSON);
|
||||
// Build the output markdown block.
|
||||
const outputBlock = buildOutputBlock(
|
||||
responseJSON,
|
||||
inputDateString,
|
||||
this.settings.dateFormat,
|
||||
this.settings.horizontalRules,
|
||||
this.settings.throughTheYearsHeader
|
||||
);
|
||||
|
||||
// 8. Insert the output block by looking for a placeholder marker.
|
||||
// Define a unique placeholder marker.
|
||||
const placeholder = "<!OTD>";
|
||||
// Insert the output block by looking for a placeholder marker defined in settings.
|
||||
const placeholder = this.settings.placeholder;
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!activeView) {
|
||||
loadingNotice.hide();
|
||||
|
|
@ -172,15 +165,6 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
new Notice("On This Day summaries inserted.");
|
||||
}
|
||||
|
||||
//
|
||||
// Construct the API Prompt, add user setting if needed
|
||||
//
|
||||
constructPrompt(yearToContent: Record<string, string>): string {
|
||||
const promptInstructions = aiPrompt + this.settings.customPrompt;
|
||||
const dataString = JSON.stringify(yearToContent);
|
||||
return `${promptInstructions}\n\nData:\n${dataString}`;
|
||||
}
|
||||
|
||||
//
|
||||
// Call the OpenAI API
|
||||
//
|
||||
|
|
@ -196,8 +180,7 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"You are a helpful assistant that summarizes daily journal entries in a strict JSON format.",
|
||||
content: sysPrompt,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
|
|
@ -236,29 +219,6 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Build the Output Markdown
|
||||
//
|
||||
buildOutputBlock(summaries: Record<string, string>): string {
|
||||
// Add horizontal rules based on settings
|
||||
let output = "## On This Day...\n";
|
||||
if (this.settings.horizontalRules.includes("Above")) {
|
||||
output = "---\n## On This Day...\n";
|
||||
}
|
||||
|
||||
// Optionally, sort the years in descending order
|
||||
const years = Object.keys(summaries).sort(
|
||||
(a, b) => parseInt(b) - parseInt(a)
|
||||
);
|
||||
for (const year of years) {
|
||||
output += `- **${year}:** ${summaries[year]}\n`;
|
||||
}
|
||||
if (this.settings.horizontalRules.includes("Below")) {
|
||||
output += "\n---";
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
addPlaceholder() {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!activeView) {
|
||||
|
|
@ -266,6 +226,6 @@ export default class OnThisDayPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
const editor = (activeView as MarkdownView).editor;
|
||||
editor.replaceSelection("<!OTD>");
|
||||
editor.replaceSelection(this.settings.placeholder);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"name": "On This Day I",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Use AI to remind yourself what has happened on this date in past years.",
|
||||
"description": "AI tools for Daily Journals",
|
||||
"author": "Ben Stuart",
|
||||
"authorUrl": "https://github.com/benstuart0/on-this-day-i-obsidian-plugin",
|
||||
"fundingUrl": "",
|
||||
|
|
|
|||
BIN
on-this-day-i-demo.gif
Normal file
BIN
on-this-day-i-demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 MiB |
|
|
@ -2,9 +2,6 @@ import { App, PluginSettingTab, Setting, TFolder } from "obsidian";
|
|||
import moment from "moment";
|
||||
import OnThisDayPlugin from "main";
|
||||
|
||||
//
|
||||
// Settings Tab
|
||||
//
|
||||
export default class OnThisDaySettingTab extends PluginSettingTab {
|
||||
plugin: OnThisDayPlugin;
|
||||
|
||||
|
|
@ -16,9 +13,8 @@ export default class OnThisDaySettingTab extends PluginSettingTab {
|
|||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl("h2", { text: "On This Day AI Settings" });
|
||||
containerEl.createEl("h2", { text: "General Settings" });
|
||||
|
||||
// Date Format Setting with dynamic example using the current setting
|
||||
const dateSetting = new Setting(containerEl)
|
||||
.setName("Date Format")
|
||||
.setDesc(
|
||||
|
|
@ -40,7 +36,6 @@ export default class OnThisDaySettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
// Daily Notes Folder Setting
|
||||
new Setting(containerEl)
|
||||
.setName("Daily Notes Folder")
|
||||
.setDesc("Select the folder where your daily notes are stored.")
|
||||
|
|
@ -67,35 +62,6 @@ export default class OnThisDaySettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
// OpenAI API Key Setting
|
||||
new Setting(containerEl)
|
||||
.setName("OpenAI API Key")
|
||||
.setDesc("Your OpenAI API key.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("sk-...")
|
||||
.setValue(this.plugin.settings.openaiApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openaiApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// Model Version Dropdown Setting
|
||||
new Setting(containerEl)
|
||||
.setName("Model Version")
|
||||
.setDesc("Select the OpenAI model to use.")
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown.addOption("gpt-3.5-turbo", "gpt-3.5-turbo");
|
||||
dropdown.addOption("gpt-4", "gpt-4");
|
||||
dropdown.setValue(this.plugin.settings.model);
|
||||
dropdown.onChange(async (value: string) => {
|
||||
this.plugin.settings.model = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
// Horizontal Rules dropdown (above, below, none, both)
|
||||
new Setting(containerEl)
|
||||
.setName("Horizontal Lines")
|
||||
.setDesc(
|
||||
|
|
@ -113,18 +79,74 @@ export default class OnThisDaySettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
// Custom instructions
|
||||
new Setting(containerEl)
|
||||
.setName("Custom Prompt Details")
|
||||
.setDesc(
|
||||
"Add custom details to the prompt if you prefer. E.g, Please prioritize things I accomplished or Only tell me positive things"
|
||||
"Add custom details to the prompt if you prefer. E.g., Please prioritize things I accomplished or only tell me positive things."
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.addTextArea((textArea) => {
|
||||
textArea
|
||||
.setValue(this.plugin.settings.customPrompt)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.customPrompt = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
// Optionally adjust the textarea appearance, e.g., set a fixed number of rows:
|
||||
textArea.inputEl.rows = 5;
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Placeholder Tag")
|
||||
.setDesc(
|
||||
"The plugin will search for this text in your active file to replace with its return block. Otherwise outputs at cursor. Default <!OTDI>"
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings.placeholder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.placeholder = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Through the Years Output Header")
|
||||
.setDesc(
|
||||
"How do you want to title the output block of the Through the Years command? The default is `On This Day`"
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings.throughTheYearsHeader)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.throughTheYearsHeader = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
containerEl.createEl("h2", { text: "OpenAI Settings" });
|
||||
new Setting(containerEl)
|
||||
.setName("Model Version")
|
||||
.setDesc("Select the OpenAI model to use.")
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown.addOption("gpt-3.5-turbo", "gpt-3.5-turbo");
|
||||
dropdown.addOption("gpt-4", "gpt-4");
|
||||
dropdown.setValue(this.plugin.settings.model);
|
||||
dropdown.onChange(async (value: string) => {
|
||||
this.plugin.settings.model = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("OpenAI API Key")
|
||||
.setDesc("Your OpenAI API key.")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("sk-...")
|
||||
.setValue(this.plugin.settings.openaiApiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openaiApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,37 +4,41 @@ import moment from "moment";
|
|||
// Utility Functions
|
||||
//
|
||||
|
||||
// Check if a string matches the expected date format ("MMMM D, YYYY")
|
||||
export function isValidDateString(dateString: string): boolean {
|
||||
const regex = /^(January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
|
||||
return regex.test(dateString);
|
||||
const acceptedDateFormats: string[] = [
|
||||
"MMMM D, YYYY", // e.g., February 6, 2025 (US)
|
||||
"MMM D, YYYY", // e.g., Feb 6, 2025 (US)
|
||||
"MM/DD/YYYY", // e.g., 02/06/2025 (US)
|
||||
"M/D/YYYY", // e.g., 2/6/2025 (US)
|
||||
"YYYY-MM-DD", // e.g., 2025-02-06 (ISO)
|
||||
"D MMMM YYYY", // e.g., 6 February 2025 (EU)
|
||||
"D MMM YYYY", // e.g., 6 Feb 2025 (EU)
|
||||
"DD/MM/YYYY", // e.g., 06/02/2025 (EU)
|
||||
"D/M/YYYY", // e.g., 6/2/2025 (EU)
|
||||
"DD-MM-YYYY", // e.g., 06-02-2025 (EU alternative)
|
||||
"D-MM-YYYY", // e.g., 6-02-2025 (less common variant)
|
||||
];
|
||||
|
||||
// Helper function to combine the accepted formats with the custom format,
|
||||
// avoiding duplicates.
|
||||
function getAllFormats(customFormat: string): string[] {
|
||||
if (acceptedDateFormats.includes(customFormat)) {
|
||||
return acceptedDateFormats;
|
||||
}
|
||||
return [...acceptedDateFormats, customFormat];
|
||||
}
|
||||
|
||||
export function isValidDateString(
|
||||
dateString: string,
|
||||
format: string = "MMMM D, YYYY"
|
||||
): boolean {
|
||||
// Use strict parsing with the combined list of formats.
|
||||
return moment(dateString, getAllFormats(format), true).isValid();
|
||||
}
|
||||
|
||||
export function parseDateFromString(
|
||||
dateString: string,
|
||||
format: string
|
||||
): Date | null {
|
||||
// Create an array of formats that includes the provided one plus several common alternatives.
|
||||
const formats = [
|
||||
format, // Use the provided format from settings first
|
||||
"MMMM D, YYYY", // e.g., February 6, 2025 (US)
|
||||
"MMM D, YYYY", // e.g., Feb 6, 2025 (US)
|
||||
"MM/DD/YYYY", // e.g., 02/06/2025 (US)
|
||||
"M/D/YYYY", // e.g., 2/6/2025 (US)
|
||||
"YYYY-MM-DD", // e.g., 2025-02-06 (ISO)
|
||||
"D MMMM YYYY", // e.g., 6 February 2025 (EU)
|
||||
"D MMM YYYY", // e.g., 6 Feb 2025 (EU)
|
||||
"DD/MM/YYYY", // e.g., 06/02/2025 (EU)
|
||||
"D/M/YYYY", // e.g., 6/2/2025 (EU)
|
||||
"DD-MM-YYYY", // e.g., 06-02-2025 (EU alternative)
|
||||
"D-MM-YYYY", // e.g., 6-02-2025 (less common variant)
|
||||
];
|
||||
|
||||
// Use strict parsing to ensure accuracy.
|
||||
const m = moment(dateString, formats, true);
|
||||
|
||||
if (m.isValid()) {
|
||||
return m.toDate();
|
||||
}
|
||||
return null;
|
||||
const m = moment(dateString, getAllFormats(format), true);
|
||||
return m.isValid() ? m.toDate() : null;
|
||||
}
|
||||
|
|
|
|||
53
src/markdownUtils.ts
Normal file
53
src/markdownUtils.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { TFile, TFolder, TAbstractFile } from "obsidian";
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Recursively collects all Markdown files (TFile) from the given folder.
|
||||
*/
|
||||
export function getMarkdownFilesInFolder(folder: TFolder): TFile[] {
|
||||
let files: TFile[] = [];
|
||||
for (const child of folder.children) {
|
||||
if (child instanceof TFile && child.extension === "md") {
|
||||
files.push(child);
|
||||
} else if (child instanceof TFolder) {
|
||||
files = files.concat(getMarkdownFilesInFolder(child));
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
export function buildOutputBlock(
|
||||
summaries: Record<string, string>,
|
||||
inputDateString: string,
|
||||
dateFormat: string,
|
||||
horizontalRules: string,
|
||||
throughTheYearsHeader: string
|
||||
): string {
|
||||
// Start with a heading using the provided header; add a horizontal rule above if specified.
|
||||
let output = `## ${throughTheYearsHeader}\n`;
|
||||
if (horizontalRules.includes("Above")) {
|
||||
output = `---\n## ${throughTheYearsHeader}\n`;
|
||||
}
|
||||
|
||||
// Create a base moment from the input date using the configured date format.
|
||||
const baseMoment = moment(inputDateString, dateFormat);
|
||||
|
||||
// Optionally, sort the years in descending order.
|
||||
const years = Object.keys(summaries).sort(
|
||||
(a, b) => parseInt(b) - parseInt(a)
|
||||
);
|
||||
|
||||
// For each year, update the base date's year, format it, and create a link.
|
||||
for (const year of years) {
|
||||
const noteMoment = baseMoment.clone().set("year", parseInt(year));
|
||||
const formattedNoteName = noteMoment.format(dateFormat);
|
||||
// Create a Markdown link in the form [[formattedNoteName|year]]
|
||||
output += `- **[[${formattedNoteName}|${year}]]:** ${summaries[year]}\n`;
|
||||
}
|
||||
|
||||
if (horizontalRules.includes("Below")) {
|
||||
output += "\n---";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
@ -1,3 +1,18 @@
|
|||
const aiPrompt =
|
||||
const userPrompt =
|
||||
"Below is a JSON object where each key is a year and each value is the full content of my daily journal for this day on that given year. For each year, please provide an interesting summary in 2-3 sentences that highlights the events of that day. Try to include who I spent time with (including their names), what I did, and how I felt. Use second-person language (you), as you are referring to me - the writer of the journals. Do not include the date in the summary. Return only a JSON object mapping each year to its summary with no extra text, headers, or footers. This is the most important rule. The output must follow this JSON format. Further personalization details: ";
|
||||
export default aiPrompt;
|
||||
|
||||
export const sysPrompt =
|
||||
"You are a helpful assistant that summarizes text, based on a user's specifications, always in a strict JSON format.";
|
||||
|
||||
//
|
||||
// Construct the API Prompt, add user setting if needed
|
||||
//
|
||||
export function constructPrompt(
|
||||
yearToContent: Record<string, string>,
|
||||
customPrompt: string
|
||||
): string {
|
||||
const promptInstructions = userPrompt + customPrompt;
|
||||
console.log(promptInstructions);
|
||||
const dataString = JSON.stringify(yearToContent);
|
||||
return `${promptInstructions}\n\nData:\n${dataString}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ export interface OnThisDayPluginSettings {
|
|||
dateFormat: string;
|
||||
horizontalRules: string;
|
||||
customPrompt: string;
|
||||
placeholder: string;
|
||||
throughTheYearsHeader: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: OnThisDayPluginSettings = {
|
||||
|
|
@ -17,4 +19,6 @@ export const DEFAULT_SETTINGS: OnThisDayPluginSettings = {
|
|||
dateFormat: "MMMM D, YYYY",
|
||||
horizontalRules: "",
|
||||
customPrompt: "",
|
||||
placeholder: "<!OTDI>",
|
||||
throughTheYearsHeader: "On This Day"
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue