mirror of
https://github.com/mousebomb/obsidian-diary-ics.git
synced 2026-07-22 05:49:47 +00:00
第一版
This commit is contained in:
parent
619bc7a1ea
commit
e61575910e
5 changed files with 2776 additions and 169 deletions
121
README.md
121
README.md
|
|
@ -1,94 +1,57 @@
|
|||
# Obsidian Sample Plugin
|
||||
# Obsidian Diary ICS
|
||||
|
||||
This is a sample plugin for Obsidian (https://obsidian.md).
|
||||
这是一个Obsidian插件,用于将Obsidian的日记系统内容同步到系统的日历应用中(如macOS日历、Windows日历等)。
|
||||
|
||||
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.
|
||||
### 生成ICS日历订阅文件
|
||||
- 插件会根据Obsidian中的日记内容,自动生成一个ICS格式的日历订阅文件(.ics)
|
||||
- 该文件将被托管在本地的一个HTTP端口上(例如:`http://127.0.0.1:19347/feed.ics`)
|
||||
- 系统日历应用可以订阅这个链接,从而实时同步Obsidian的日记内容到系统日历中
|
||||
|
||||
## First time developing plugins?
|
||||
### 日记内容解析规则
|
||||
- 插件会解析Obsidian中的日记笔记文件(通常是按日期命名的Markdown文件)
|
||||
- 从这些文件中提取出一级或二级标题(由用户配置)
|
||||
- 每个提取出的标题将作为一条日历条目,对应当天(即文件名中的日期)
|
||||
|
||||
Quick starting guide for new plugin devs:
|
||||
### 日历条目详情
|
||||
每个日历条目(事件)将包含以下内容:
|
||||
- **标题**:从日记文件中提取的一级或二级标题
|
||||
- **备注(Description)**:包含一个可点击的链接,格式为:`obsidian://open?vault=你的库名&file=日记文件路径`,点击后可以直接跳转回Obsidian的对应日记文件中
|
||||
|
||||
- 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
|
||||
假设你有一个日记文件:`2025-05-14.md`,内容如下:
|
||||
|
||||
- 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.
|
||||
```markdown
|
||||
# 今天的工作总结
|
||||
|
||||
> 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`
|
||||
## 上午任务
|
||||
- 完成项目A的模块1
|
||||
|
||||
## 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:
|
||||
用户在插件设置中设置了提取所有二级标题。则,插件会提取出两个日历条目:
|
||||
- 事件1:标题为"上午任务",备注中包含链接
|
||||
- 事件2:标题为"下午任务",备注中包含链接
|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": {
|
||||
"Buy Me a Coffee": "https://buymeacoffee.com",
|
||||
"GitHub Sponsor": "https://github.com/sponsors",
|
||||
"Patreon": "https://www.patreon.com/"
|
||||
}
|
||||
}
|
||||
```
|
||||
系统日历订阅了 `http://127.0.0.1:19347/feed.ics` 后,就能看到这两个事件。
|
||||
|
||||
## API Documentation
|
||||
## 使用方法
|
||||
|
||||
See https://github.com/obsidianmd/obsidian-api
|
||||
1. 在Obsidian中安装并启用此插件
|
||||
2. 在插件设置中配置:
|
||||
- 要提取的标题级别(一级或二级标题)
|
||||
- HTTP服务器端口(默认为19347)
|
||||
3. 复制插件提供的ICS订阅链接
|
||||
4. 在系统日历应用中添加该订阅链接
|
||||
5. 现在,你的Obsidian日记内容将自动同步到系统日历中
|
||||
|
||||
## 开发信息
|
||||
|
||||
- 本插件使用TypeScript开发
|
||||
- 使用Obsidian API访问日记文件内容并监听文件变化
|
||||
- 启动本地HTTP服务器提供ICS文件
|
||||
- 按照ICS标准生成日历文件
|
||||
|
|
|
|||
328
main.ts
328
main.ts
|
|
@ -1,85 +1,70 @@
|
|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
||||
import * as http from 'http';
|
||||
import { createEvents, EventAttributes } from 'ics';
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
interface DiaryIcsSettings {
|
||||
port: number;
|
||||
headingLevel: string;
|
||||
includeContent: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
mySetting: 'default'
|
||||
const DEFAULT_SETTINGS: DiaryIcsSettings = {
|
||||
port: 19347,
|
||||
headingLevel: 'h2',
|
||||
includeContent: true
|
||||
}
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
export default class DiaryIcsPlugin extends Plugin {
|
||||
settings: DiaryIcsSettings;
|
||||
server: http.Server | null = null;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// 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!');
|
||||
// 添加图标到左侧边栏
|
||||
const ribbonIconEl = this.addRibbonIcon('calendar-with-checkmark', 'Diary ICS', (evt: MouseEvent) => {
|
||||
// 点击图标时显示ICS订阅链接
|
||||
new Notice(`ICS订阅链接: http://127.0.0.1:${this.settings.port}/feed.ics`);
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
|
||||
// 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');
|
||||
statusBarItemEl.setText(`ICS: http://127.0.0.1:${this.settings.port}/feed.ics`);
|
||||
|
||||
// This adds a simple command that can be triggered anywhere
|
||||
// 添加命令:复制ICS订阅链接
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-simple',
|
||||
name: 'Open sample modal (simple)',
|
||||
id: 'copy-ics-url',
|
||||
name: '复制ICS订阅链接',
|
||||
callback: () => {
|
||||
new SampleModal(this.app).open();
|
||||
const url = `http://127.0.0.1:${this.settings.port}/feed.ics`;
|
||||
navigator.clipboard.writeText(url);
|
||||
new Notice('ICS订阅链接已复制到剪贴板');
|
||||
}
|
||||
});
|
||||
// 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();
|
||||
}
|
||||
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
// 添加设置选项卡
|
||||
this.addSettingTab(new DiaryIcsSettingTab(this.app, this));
|
||||
|
||||
// 启动HTTP服务器
|
||||
this.startServer();
|
||||
|
||||
// 监听文件变化,以便更新ICS内容
|
||||
this.registerEvent(
|
||||
this.app.vault.on('modify', (file) => {
|
||||
if (this.isDiaryFile(file)) {
|
||||
console.log('日记文件已更新:', file.path);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
||||
|
||||
// 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() {
|
||||
|
||||
// 关闭HTTP服务器
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
console.log('ICS HTTP服务器已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
|
|
@ -88,29 +73,152 @@ export default class MyPlugin extends Plugin {
|
|||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
|
||||
// 重启服务器以应用新设置
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
}
|
||||
this.startServer();
|
||||
}
|
||||
|
||||
// 启动HTTP服务器提供ICS文件
|
||||
startServer() {
|
||||
const port = this.settings.port;
|
||||
|
||||
this.server = http.createServer(async (req, res) => {
|
||||
if (req.url === '/feed.ics') {
|
||||
try {
|
||||
const icsContent = await this.generateIcsContent();
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/calendar',
|
||||
'Content-Disposition': 'attachment; filename="obsidian-diary.ics"'
|
||||
});
|
||||
res.end(icsContent);
|
||||
console.log('已提供ICS文件');
|
||||
} catch (error) {
|
||||
console.error('生成ICS文件时出错:', error);
|
||||
res.writeHead(500);
|
||||
res.end('生成ICS文件时出错');
|
||||
}
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('未找到');
|
||||
}
|
||||
});
|
||||
|
||||
this.server.listen(port, '127.0.0.1', () => {
|
||||
console.log(`ICS HTTP服务器已启动: http://127.0.0.1:${port}/feed.ics`);
|
||||
new Notice(`ICS服务器已启动: http://127.0.0.1:${port}/feed.ics`);
|
||||
});
|
||||
|
||||
this.server.on('error', (error) => {
|
||||
console.error('HTTP服务器错误:', error);
|
||||
new Notice(`HTTP服务器错误: ${error.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 判断文件是否为日记文件(基于文件名格式:YYYY-MM-DD.md)
|
||||
isDiaryFile(file: TFile): boolean {
|
||||
return file.extension === 'md' && /^\d{4}-\d{2}-\d{2}$/.test(file.basename);
|
||||
}
|
||||
|
||||
// 解析日记文件,提取标题
|
||||
async parseDiaryFile(file: TFile): Promise<{title: string, content: string}[]> {
|
||||
const content = await this.app.vault.read(file);
|
||||
const entries: {title: string, content: string}[] = [];
|
||||
|
||||
// 根据设置选择要提取的标题级别
|
||||
const headingPattern = this.settings.headingLevel === 'h1'
|
||||
? '^# (.+)$'
|
||||
: '^## (.+)$';
|
||||
|
||||
// 使用字符串分割方法而不是正则表达式的exec方法,避免lastIndex问题
|
||||
const lines = content.split('\n');
|
||||
let currentTitle = '';
|
||||
let currentContent = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const headingMatch = new RegExp(headingPattern).exec(line);
|
||||
|
||||
if (headingMatch) {
|
||||
// 如果已经有标题,保存之前的条目
|
||||
if (currentTitle) {
|
||||
entries.push({
|
||||
title: currentTitle,
|
||||
content: currentContent.join('\n').trim()
|
||||
});
|
||||
currentContent = [];
|
||||
}
|
||||
|
||||
currentTitle = headingMatch[1];
|
||||
} else if (currentTitle) {
|
||||
currentContent.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加最后一个条目
|
||||
if (currentTitle) {
|
||||
entries.push({
|
||||
title: currentTitle,
|
||||
content: currentContent.join('\n').trim()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
// 生成ICS文件内容
|
||||
async generateIcsContent(): Promise<string> {
|
||||
const events: EventAttributes[] = [];
|
||||
const vaultName = this.app.vault.getName();
|
||||
console.log (" 生成ICS文件内容: ", vaultName);
|
||||
|
||||
// 获取所有日记文件
|
||||
const files = this.app.vault.getMarkdownFiles()
|
||||
.filter(file => this.isDiaryFile(file));
|
||||
|
||||
console.log (" 生成ICS文件内容: ", files.length, " 个日记文件");
|
||||
for (const file of files) {
|
||||
// 从文件名解析日期
|
||||
const [year, month, day] = file.basename.split('-').map(Number);
|
||||
const entries = await this.parseDiaryFile(file);
|
||||
console.log (" 解析日记文件: ", file.path, " 解析条目: ", entries.length, " 条");
|
||||
for (const entry of entries) {
|
||||
// 创建事件
|
||||
events.push({
|
||||
title: entry.title,
|
||||
description: this.settings.includeContent
|
||||
? `${entry.content}\n\n点击打开: obsidian://open?vault=${encodeURIComponent(vaultName)}&file=${encodeURIComponent(file.path)}`
|
||||
: `点击打开: obsidian://open?vault=${encodeURIComponent(vaultName)}&file=${encodeURIComponent(file.path)}`,
|
||||
start: [year, month, day],
|
||||
status: 'CONFIRMED',
|
||||
busyStatus: 'FREE'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 使用ics库生成ICS内容
|
||||
return new Promise((resolve, reject) => {
|
||||
createEvents(events, (error, value) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SampleModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
}
|
||||
class DiaryIcsSettingTab extends PluginSettingTab {
|
||||
plugin: DiaryIcsPlugin;
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.setText('Woah!');
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
class SampleSettingTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
constructor(app: App, plugin: DiaryIcsPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
|
@ -120,15 +228,65 @@ class SampleSettingTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', {text: 'Diary ICS 设置'});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Setting #1')
|
||||
.setDesc('It\'s a secret')
|
||||
.setName('HTTP服务器端口')
|
||||
.setDesc('本地HTTP服务器使用的端口号')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your secret')
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.setPlaceholder('99347')
|
||||
.setValue(this.plugin.settings.port.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.mySetting = value;
|
||||
const port = parseInt(value);
|
||||
if (!isNaN(port) && port > 0 && port < 65536) {
|
||||
this.plugin.settings.port = port;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('标题级别')
|
||||
.setDesc('从日记中提取哪一级标题作为日历条目')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('h1', '一级标题 (#)')
|
||||
.addOption('h2', '二级标题 (##)')
|
||||
.setValue(this.plugin.settings.headingLevel)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.headingLevel = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('包含内容')
|
||||
.setDesc('在日历事件描述中包含标题下的内容')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.includeContent)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.includeContent = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
// 显示当前ICS订阅链接
|
||||
containerEl.createEl('h3', {text: 'ICS订阅链接'});
|
||||
const linkEl = containerEl.createEl('div', {text: `http://127.0.0.1:${this.plugin.settings.port}/feed.ics`});
|
||||
linkEl.style.padding = '10px';
|
||||
linkEl.style.backgroundColor = '#f5f5f5';
|
||||
linkEl.style.borderRadius = '5px';
|
||||
linkEl.style.marginBottom = '20px';
|
||||
|
||||
// 添加复制按钮
|
||||
const copyButton = containerEl.createEl('button', {text: '复制链接'});
|
||||
copyButton.style.marginBottom = '20px';
|
||||
copyButton.addEventListener('click', () => {
|
||||
const url = `http://127.0.0.1:${this.plugin.settings.port}/feed.ics`;
|
||||
navigator.clipboard.writeText(url);
|
||||
new Notice('ICS订阅链接已复制到剪贴板');
|
||||
});
|
||||
|
||||
// 添加使用说明
|
||||
containerEl.createEl('h3', {text: '使用说明'});
|
||||
containerEl.createEl('p', {text: '1. 复制上面的ICS订阅链接'});
|
||||
containerEl.createEl('p', {text: '2. 在系统日历应用中添加该订阅链接'});
|
||||
containerEl.createEl('p', {text: '3. 确保Obsidian在运行状态,以便HTTP服务器能够提供ICS文件'});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"id": "diary-ics",
|
||||
"name": "diary-ics",
|
||||
"name": "Obsidian Diary ICS",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Sync Obsidian diary entries to system calendar via ICS feed.",
|
||||
"description": "将Obsidian日记内容同步到系统日历应用中,通过ICS订阅链接实现实时同步。",
|
||||
"author": "Obsidian",
|
||||
"authorUrl": "https://mousebomb.org",
|
||||
"fundingUrl": "https://obsidian.md/pricing",
|
||||
"isDesktopOnly": true
|
||||
}
|
||||
|
|
|
|||
2483
package-lock.json
generated
Normal file
2483
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"name": "obsidian-diary-ics",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"description": "Sync Obsidian diary entries to system calendar via ICS feed.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
|
|
@ -11,6 +11,10 @@
|
|||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ics": "^3.1.0",
|
||||
"http": "^0.0.1-security"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue