Initial commit: Waypoint Obsidian plugin with calendar, recent files, and bookmarks
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
# Waypoint Plugin — Implementation Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A sidebar plugin for Obsidian that combines three panels into one coherent view: a **periodic note calendar** (day/week/month/quarter/year), a **recent files list**, and a **custom favorites/bookmarks system** with rename, icons, and grouping.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features (from requirements)
|
||||||
|
|
||||||
|
1. **Calendar panel** — shows a month grid with every day clickable
|
||||||
|
2. **Period indicators** — day, week, month, quarter, year all displayed and clickable
|
||||||
|
3. **Note creation on click** — clicking a period creates a note at a configurable path with a configurable template
|
||||||
|
4. **Recent files** — list of recently opened/edited files
|
||||||
|
5. **Favorites/Bookmarks** — custom bookmarks (distinct from Obsidian native bookmarks) with:
|
||||||
|
- Command: "Add current file as Waypoint bookmark"
|
||||||
|
- Rename without renaming the underlying file
|
||||||
|
- Assign an icon (from Lucide icons)
|
||||||
|
- Right-click context menu: rename, change icon, remove, move
|
||||||
|
- Foldable groups and indented hierarchy
|
||||||
|
6. **Settings** — configure folder paths and templates for each period type
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Approach
|
||||||
|
|
||||||
|
- **Plain ItemView** (not React) — simpler, matches recent-files-obsidian's approach, avoids extra dependencies
|
||||||
|
- **Single view type** `waypoint-view` that contains all three panels stacked vertically
|
||||||
|
- **Plugin data** stored via `Plugin.loadData()` / `Plugin.saveData()` for bookmarks and recent files
|
||||||
|
- **Settings** via `PluginSettingTab`
|
||||||
|
|
||||||
|
### Why not React?
|
||||||
|
|
||||||
|
The daily-note-calendar plugin uses React, but that adds complexity (esbuild config for JSX, react-dom dependency). The recent-files plugin does everything with plain Obsidian DOM helpers (`createDiv`, `setIcon`, etc.) and is much simpler. We can build a clean, native-feeling UI without React. If the calendar grid becomes complex, we can reconsider.
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
waypoint/
|
||||||
|
├── manifest.json
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── esbuild.config.mjs
|
||||||
|
├── styles.css
|
||||||
|
├── src/
|
||||||
|
│ ├── main.ts # Plugin entry point
|
||||||
|
│ ├── settings.ts # Settings interface + defaults + SettingTab
|
||||||
|
│ ├── views/
|
||||||
|
│ │ ├── waypoint-view.ts # Main ItemView (orchestrates panels)
|
||||||
|
│ │ ├── calendar-panel.ts # Calendar month grid + period indicators
|
||||||
|
│ │ ├── recent-files-panel.ts # Recent files list
|
||||||
|
│ │ └── favorites-panel.ts # Favorites/bookmarks tree
|
||||||
|
│ ├── models/
|
||||||
|
│ │ └── bookmark.ts # Bookmark item/group interfaces
|
||||||
|
│ ├── services/
|
||||||
|
│ │ ├── calendar-service.ts # Date math, period detection, note creation
|
||||||
|
│ │ ├── recent-files-service.ts # Track and filter recent files
|
||||||
|
│ │ └── favorites-service.ts # CRUD for custom bookmarks
|
||||||
|
│ └── utils/
|
||||||
|
│ ├── date-utils.ts # Date formatting, period navigation
|
||||||
|
│ └── icon-utils.ts # Icon picker modal
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Details
|
||||||
|
|
||||||
|
### 1. Calendar Panel (`calendar-panel.ts`)
|
||||||
|
|
||||||
|
**Visual layout (top to bottom):**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Q3 · June · 1st, 2026 │ ← Breadcrumb: quarter, month, day (all clickable)
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ ◀ June 2026 ▶ │ ← Month+Year header with nav
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ Mon Tue Wed Thu Fri Sat Sun │ ← Day-of-week headers
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ W22 1 2 3 4 5 6 │ ← Week rows (week number + 7 days)
|
||||||
|
│ W23 7 8 9 10 11 12 13 │
|
||||||
|
│ W24 14 15 16 17 18 19 20 │
|
||||||
|
│ W25 21 22 23 24 25 26 27 │
|
||||||
|
│ W26 28 29 30 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ ◀ [Today] ▶ │ ← Today navigation
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **Click a day cell** → open or create the daily note for that date
|
||||||
|
- **Click week number** → open/create the weekly note
|
||||||
|
- **Click breadcrumb quarter** → open/create the quarterly note
|
||||||
|
- **Click breadcrumb month** → open/create the monthly note
|
||||||
|
- **Click breadcrumb day** → open/create the daily note
|
||||||
|
- **Left/right arrows on month header** → navigate months
|
||||||
|
- **"Today" button** → jump to current month
|
||||||
|
- **Dot indicator** → show which days already have notes
|
||||||
|
- **Today highlight** → accent-colored border on current day
|
||||||
|
- **Week numbers** displayed as the first column, using ISO week numbering (weeks start on Monday)
|
||||||
|
|
||||||
|
**Note creation logic:**
|
||||||
|
1. Check if a note exists at `{folder}/{nameTemplate}` using the date
|
||||||
|
2. If it exists, open it
|
||||||
|
3. If not, create it using the configured template file (via Obsidian API)
|
||||||
|
4. Apply the `type` property matching the period type (daily-note, weekly-note, etc.)
|
||||||
|
|
||||||
|
### 2. Recent Files Panel (`recent-files-panel.ts`)
|
||||||
|
|
||||||
|
Based closely on the recent-files-obsidian plugin:
|
||||||
|
- Track file opens/edits via `app.workspace.on('file-open')`
|
||||||
|
- Store as ordered list of `{ path, basename }`
|
||||||
|
- Display in a `nav-folder` styled list (matches Obsidian's file explorer look)
|
||||||
|
- Each item shows:
|
||||||
|
- File name (supporting frontmatter-title plugin if available)
|
||||||
|
- Hover preview via `hover-link` event
|
||||||
|
- Drag support (wikilink insertion)
|
||||||
|
- Right-click: "Open in new tab" + Obsidian file menu
|
||||||
|
- Middle-click / Ctrl+click: open in new pane
|
||||||
|
- X button on hover to remove from list
|
||||||
|
- Respect omitted paths/tags filter (from settings)
|
||||||
|
- Max length configurable
|
||||||
|
|
||||||
|
### 3. Favorites / Bookmarks Panel (`favorites-panel.ts`)
|
||||||
|
|
||||||
|
**Data model:**
|
||||||
|
```typescript
|
||||||
|
interface BookmarkItem {
|
||||||
|
id: string; // unique ID
|
||||||
|
type: 'file' | 'group';
|
||||||
|
label: string; // display name (can differ from file name)
|
||||||
|
filePath: string; // path to the Obsidian file (empty for groups)
|
||||||
|
icon: string; // Lucide icon name (default: 'file')
|
||||||
|
children: BookmarkItem[]; // for groups
|
||||||
|
collapsed: boolean; // group fold state
|
||||||
|
indent: number; // indent level (0 = top level)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Visual layout:**
|
||||||
|
```
|
||||||
|
── Waypoint Bookmarks ────
|
||||||
|
📁 Projects ← group (collapsible)
|
||||||
|
📄 Fabrique ← file bookmark
|
||||||
|
📄 Haro dashboard ← file bookmark
|
||||||
|
📁 References ← group
|
||||||
|
📄 CSS grid notes
|
||||||
|
📄 Inbox ← ungrouped bookmark
|
||||||
|
```
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **Click file bookmark** → opens the file
|
||||||
|
- **Click group** → toggles collapse/expand
|
||||||
|
- **Drag to reorder** (stretch goal — use Obsidian's drag system)
|
||||||
|
- **Right-click on file bookmark** → context menu:
|
||||||
|
- Rename bookmark
|
||||||
|
- Change icon (opens icon picker)
|
||||||
|
- Remove bookmark
|
||||||
|
- Move to group → submenu of existing groups
|
||||||
|
- **Right-click on group** → context menu:
|
||||||
|
- Rename group
|
||||||
|
- Change icon
|
||||||
|
- Remove group (and all children)
|
||||||
|
- Add bookmark to this group
|
||||||
|
- New sub-group
|
||||||
|
- **Right-click on empty space** → context menu:
|
||||||
|
- New group
|
||||||
|
- Add current file
|
||||||
|
|
||||||
|
**Add current file command:**
|
||||||
|
- Command ID: `waypoint:add-bookmark`
|
||||||
|
- Picks up the active file
|
||||||
|
- Shows a prompt to set the display name (defaults to file's basename)
|
||||||
|
- Shows an icon picker (default: file icon)
|
||||||
|
- Adds to root level (or active group if one is right-clicked)
|
||||||
|
|
||||||
|
### 4. Settings Tab (`settings.ts`)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌── Waypoint Settings ──────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 📅 Calendar │
|
||||||
|
│ ┌─ General ─────────────────────────┐ │
|
||||||
|
│ │ First day of week [Monday ▼] │ │
|
||||||
|
│ │ Week number standard [ISO ▼] │ │
|
||||||
|
│ │ Show note indicators [toggle] │ │
|
||||||
|
│ └──────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─ Periodic Note Paths ─────────────┐ │
|
||||||
|
│ │ Daily note │ │
|
||||||
|
│ │ Folder: [periodic/daily ] │ │
|
||||||
|
│ │ Template: [templates/daily ] │ │
|
||||||
|
│ │ Name: [yyyy-MM-dd ] │ │
|
||||||
|
│ │ Type: [daily-note ] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Weekly note │ │
|
||||||
|
│ │ Folder: [periodic/weekly ] │ │
|
||||||
|
│ │ Template: [templates/weekly ] │ │
|
||||||
|
│ │ Name: [GGGG-[W]WW ] │ │
|
||||||
|
│ │ Type: [weekly-note ] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Monthly note │ │
|
||||||
|
│ │ Folder: [periodic/monthly ] │ │
|
||||||
|
│ │ ... │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Quarterly note │ │
|
||||||
|
│ │ ... │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Yearly note │ │
|
||||||
|
│ │ ... │ │
|
||||||
|
│ └──────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ 📄 Recent Files │
|
||||||
|
│ ┌──────────────────────────────────┐ │
|
||||||
|
│ │ Max items: [50 ] │ │
|
||||||
|
│ │ Update on: [File opened ▼] │ │
|
||||||
|
│ │ Omitted paths: [textarea ] │ │
|
||||||
|
│ │ Omitted tags: [textarea ] │ │
|
||||||
|
│ └──────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ⭐ Favorites │
|
||||||
|
│ ┌──────────────────────────────────┐ │
|
||||||
|
│ │ (No specific settings yet) │ │
|
||||||
|
│ └──────────────────────────────────┘ │
|
||||||
|
└────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### Step 1: Project scaffolding
|
||||||
|
- Create `manifest.json`, `package.json`, `tsconfig.json`, `esbuild.config.mjs`
|
||||||
|
- Install deps: `obsidian`, `builtin-modules`, typescript
|
||||||
|
- Create folder structure
|
||||||
|
|
||||||
|
### Step 2: Settings
|
||||||
|
- Define interfaces: `WaypointSettings`, `PeriodNoteSettings`
|
||||||
|
- Defaults matching O-vault convention:
|
||||||
|
- Daily: `periodic/daily/`, template `Templates/Daily note`, name `yyyy-MM-dd`
|
||||||
|
- Weekly: `periodic/weekly/`, template `Templates/Weekly note`, name `GGGG-[W]WW`
|
||||||
|
- Monthly: `periodic/monthly/`, template `Templates/Monthly note`, name `yyyy-MM`
|
||||||
|
- Quarterly: `periodic/quarterly/`, template `Templates/Quarterly note`, name `yyyy-[Q]Q`
|
||||||
|
- Yearly: `periodic/yearly/`, template `Templates/Yearly note`, name `yyyy`
|
||||||
|
- Implement `WaypointSettingTab` with sections for each period + recent files
|
||||||
|
|
||||||
|
### Step 3: Calendar Service
|
||||||
|
- `getMonthGrid(year, month, firstDayOfWeek)` → array of weeks with day objects
|
||||||
|
- `getPeriodFromDate(date)` → { day, week, month, quarter, year }
|
||||||
|
- `findOrCreateNote(period, settings)` → checks vault, creates if missing
|
||||||
|
- Date formatting using `moment()` (bundled in Obsidian)
|
||||||
|
|
||||||
|
### Step 4: Calendar Panel (ItemView section)
|
||||||
|
- Render the calendar grid using Obsidian DOM (`createDiv`, `createSpan`)
|
||||||
|
- Period header row (year/quarter/week)
|
||||||
|
- Month header with nav arrows
|
||||||
|
- Day-of-week header row
|
||||||
|
- Day cells (with today highlight, note indicators, other-month dimming)
|
||||||
|
- Arrow navigation for months
|
||||||
|
- Click handlers for each period type
|
||||||
|
|
||||||
|
### Step 5: Recent Files Service
|
||||||
|
- Track `file-open` events
|
||||||
|
- Maintain ordered list (max length, dedup)
|
||||||
|
- Support omitted paths/tags filtering
|
||||||
|
|
||||||
|
### Step 6: Recent Files Panel
|
||||||
|
- Render the list in `nav-folder` style
|
||||||
|
- Click, hover, drag, right-click, middle-click, delete button
|
||||||
|
|
||||||
|
### Step 7: Favorites Service
|
||||||
|
- CRUD operations on `BookmarkItem[]`
|
||||||
|
- Persist via `Plugin.loadData/saveData`
|
||||||
|
- Add current file command
|
||||||
|
- Rename, icon change, remove, grouping
|
||||||
|
|
||||||
|
### Step 8: Favorites Panel
|
||||||
|
- Render tree with collapsible groups
|
||||||
|
- Context menus for items and groups
|
||||||
|
- Icon display using `setIcon()`
|
||||||
|
- Drag support (stretch)
|
||||||
|
|
||||||
|
### Step 9: Waypoint View (main orchestrator)
|
||||||
|
- `ItemView` subclass
|
||||||
|
- On open: create three sections inside `contentEl`
|
||||||
|
- Redraw on data changes
|
||||||
|
- Register as sidebar view type `waypoint-view`
|
||||||
|
|
||||||
|
### Step 10: Main Plugin
|
||||||
|
- `onload()`: load data, register view, register commands, register events, add settings tab
|
||||||
|
- Auto-open on right sidebar on first load
|
||||||
|
- Commands:
|
||||||
|
- `waypoint:open-view` → reveal the waypoint sidebar
|
||||||
|
- `waypoint:add-bookmark` → add current file as bookmark
|
||||||
|
- `waypoint:go-to-today` → open today's daily note
|
||||||
|
- Events:
|
||||||
|
- `file-open` → update recent files + redraw
|
||||||
|
- `create`, `delete`, `rename` → update calendar note indicators + redraw
|
||||||
|
- Midnight check → refresh today highlight
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
1. **No React** — Using native Obsidian DOM APIs avoids build complexity and keeps the bundle small. The calendar grid is straightforward enough to render imperatively.
|
||||||
|
|
||||||
|
2. **Flat data storage** — Bookmarks and recent files stored via Plugin.loadData/saveData as JSON. This makes them portable and compatible with Obsidian sync.
|
||||||
|
|
||||||
|
3. **Separate from native bookmarks** — We never touch `app.internalPlugins.getEnabledPluginById('bookmarks')`. These are entirely custom bookmarks with their own data store.
|
||||||
|
|
||||||
|
4. **Icon picker** — We'll build a simple icon picker modal using Obsidian's `SuggestModal` or a grid of Lucide icons that are commonly used (file, folder, star, heart, bookmark, flag, pin, etc.). We won't implement a searchable full icon browser initially — a curated selection of ~40 icons plus a text input for any Lucide icon name.
|
||||||
|
|
||||||
|
5. **O-vault aware** — Default paths match Olivier's vault structure. All settings are overridable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stretch Goals (after MVP)
|
||||||
|
|
||||||
|
- Drag-and-drop reordering in favorites panel
|
||||||
|
- Search/filter within bookmarks
|
||||||
|
- Multiple bookmark lists / profiles
|
||||||
|
- Sync bookmarks with Obsidian Sync
|
||||||
|
- Show note previews on calendar hover
|
||||||
|
- Custom CSS classes per bookmark (color coding)
|
||||||
|
- Nested groups beyond 2 levels
|
||||||
|
- Collapse/expand all in favorites
|
||||||
|
- Keyboard shortcuts for bookmark navigation
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import esbuild from 'esbuild';
|
||||||
|
import process from 'process';
|
||||||
|
import builtins from 'builtin-modules';
|
||||||
|
|
||||||
|
const banner = `/*
|
||||||
|
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
|
||||||
|
const prod = process.argv[2] === 'production';
|
||||||
|
|
||||||
|
const context = await esbuild.context({
|
||||||
|
banner: { js: banner },
|
||||||
|
entryPoints: ['src/main.ts'],
|
||||||
|
bundle: true,
|
||||||
|
external: [
|
||||||
|
'obsidian',
|
||||||
|
'electron',
|
||||||
|
'@codemirror/autocomplete',
|
||||||
|
'@codemirror/collab',
|
||||||
|
'@codemirror/commands',
|
||||||
|
'@codemirror/language',
|
||||||
|
'@codemirror/lint',
|
||||||
|
'@codemirror/search',
|
||||||
|
'@codemirror/state',
|
||||||
|
'@codemirror/view',
|
||||||
|
'@lezer/common',
|
||||||
|
'@lezer/highlight',
|
||||||
|
'@lezer/lr',
|
||||||
|
...builtins,
|
||||||
|
],
|
||||||
|
format: 'cjs',
|
||||||
|
target: 'es2018',
|
||||||
|
logLevel: 'info',
|
||||||
|
sourcemap: prod ? false : 'inline',
|
||||||
|
treeShaking: true,
|
||||||
|
outfile: 'main.js',
|
||||||
|
minify: prod,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prod) {
|
||||||
|
await context.rebuild();
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
await context.watch();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"id": "waypoint",
|
||||||
|
"name": "Waypoint",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"minAppVersion": "0.16.3",
|
||||||
|
"description": "Calendar, recent files, and custom bookmarks sidebar.",
|
||||||
|
"author": "Olivier",
|
||||||
|
"isDesktopOnly": false
|
||||||
|
}
|
||||||
Generated
+639
@@ -0,0 +1,639 @@
|
|||||||
|
{
|
||||||
|
"name": "waypoint",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "waypoint",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"builtin-modules": "4.0.0",
|
||||||
|
"esbuild": "^0.25.0",
|
||||||
|
"obsidian": "latest",
|
||||||
|
"typescript": "^5.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.38.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
|
||||||
|
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"crelt": "^1.0.6",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/codemirror": {
|
||||||
|
"version": "5.60.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
|
||||||
|
"integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/tern": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/estree": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/tern": {
|
||||||
|
"version": "0.23.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||||
|
"integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/builtin-modules": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-p1n8zyCkt1BVrKNFymOHjcDSAl7oq/gUvfgULv2EblgpPVQlQr9yHnWjg9IJ2MhfwPqiYqMMrr01OY7yQoK2yA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.25.12",
|
||||||
|
"@esbuild/android-arm": "0.25.12",
|
||||||
|
"@esbuild/android-arm64": "0.25.12",
|
||||||
|
"@esbuild/android-x64": "0.25.12",
|
||||||
|
"@esbuild/darwin-arm64": "0.25.12",
|
||||||
|
"@esbuild/darwin-x64": "0.25.12",
|
||||||
|
"@esbuild/freebsd-arm64": "0.25.12",
|
||||||
|
"@esbuild/freebsd-x64": "0.25.12",
|
||||||
|
"@esbuild/linux-arm": "0.25.12",
|
||||||
|
"@esbuild/linux-arm64": "0.25.12",
|
||||||
|
"@esbuild/linux-ia32": "0.25.12",
|
||||||
|
"@esbuild/linux-loong64": "0.25.12",
|
||||||
|
"@esbuild/linux-mips64el": "0.25.12",
|
||||||
|
"@esbuild/linux-ppc64": "0.25.12",
|
||||||
|
"@esbuild/linux-riscv64": "0.25.12",
|
||||||
|
"@esbuild/linux-s390x": "0.25.12",
|
||||||
|
"@esbuild/linux-x64": "0.25.12",
|
||||||
|
"@esbuild/netbsd-arm64": "0.25.12",
|
||||||
|
"@esbuild/netbsd-x64": "0.25.12",
|
||||||
|
"@esbuild/openbsd-arm64": "0.25.12",
|
||||||
|
"@esbuild/openbsd-x64": "0.25.12",
|
||||||
|
"@esbuild/openharmony-arm64": "0.25.12",
|
||||||
|
"@esbuild/sunos-x64": "0.25.12",
|
||||||
|
"@esbuild/win32-arm64": "0.25.12",
|
||||||
|
"@esbuild/win32-ia32": "0.25.12",
|
||||||
|
"@esbuild/win32-x64": "0.25.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.29.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||||
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/obsidian": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-PHw5+SAPlJ0S3leFvJ0wgFg63Z3DavxL6+d1ll+8toXR2ZlYKc1rMWqdUv9LgUbTwPQUyY6yfhOMMivampRRiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/codemirror": "5.60.8",
|
||||||
|
"moment": "2.29.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/state": "6.5.0",
|
||||||
|
"@codemirror/view": "6.38.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "waypoint",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Calendar, recent files, and custom bookmarks sidebar.",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node esbuild.config.mjs",
|
||||||
|
"build": "node esbuild.config.mjs production"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Olivier",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"builtin-modules": "4.0.0",
|
||||||
|
"esbuild": "^0.25.0",
|
||||||
|
"obsidian": "latest",
|
||||||
|
"typescript": "^5.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+352
@@ -0,0 +1,352 @@
|
|||||||
|
// ── Main plugin entry ──
|
||||||
|
|
||||||
|
import {
|
||||||
|
Plugin,
|
||||||
|
WorkspaceLeaf,
|
||||||
|
ItemView,
|
||||||
|
Notice,
|
||||||
|
TFile,
|
||||||
|
TAbstractFile,
|
||||||
|
moment,
|
||||||
|
} from 'obsidian';
|
||||||
|
import { WaypointSettings, DEFAULT_SETTINGS, PeriodNoteSettings } from 'src/settings';
|
||||||
|
import { WaypointSettingTab } from 'src/settings-tab';
|
||||||
|
import { WaypointView, WAYPOINT_VIEW_TYPE } from 'src/views/waypoint-view';
|
||||||
|
import { BookmarkItem, WaypointData } from 'src/models/bookmark';
|
||||||
|
|
||||||
|
const DEFAULT_DATA: WaypointData = {
|
||||||
|
bookmarks: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class WaypointPlugin extends Plugin {
|
||||||
|
public settings: WaypointSettings;
|
||||||
|
public waypointData: WaypointData;
|
||||||
|
public recentFiles: { path: string; basename: string }[] = [];
|
||||||
|
|
||||||
|
async onload(): Promise<void> {
|
||||||
|
console.debug('Waypoint: loading plugin v' + this.manifest.version);
|
||||||
|
|
||||||
|
// Load persisted data
|
||||||
|
await this.loadSettings();
|
||||||
|
await this.loadWaypointData();
|
||||||
|
|
||||||
|
// Register the sidebar view
|
||||||
|
this.registerView(
|
||||||
|
WAYPOINT_VIEW_TYPE,
|
||||||
|
(leaf: WorkspaceLeaf) => new WaypointView(leaf, this),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register settings tab
|
||||||
|
this.addSettingTab(new WaypointSettingTab(
|
||||||
|
this.app,
|
||||||
|
this,
|
||||||
|
this.settings,
|
||||||
|
() => this.redrawAll(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// ── Commands ──
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: 'waypoint-open-view',
|
||||||
|
name: 'Open Waypoint sidebar',
|
||||||
|
callback: async () => {
|
||||||
|
const leaves = this.app.workspace.getLeavesOfType(WAYPOINT_VIEW_TYPE);
|
||||||
|
if (leaves.length > 0) {
|
||||||
|
await this.app.workspace.revealLeaf(leaves[0]);
|
||||||
|
} else {
|
||||||
|
const leaf = this.app.workspace.getLeftLeaf(false);
|
||||||
|
if (leaf) {
|
||||||
|
await leaf.setViewState({ type: WAYPOINT_VIEW_TYPE });
|
||||||
|
await this.app.workspace.revealLeaf(leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: 'waypoint-add-bookmark',
|
||||||
|
name: 'Add current file as Waypoint bookmark',
|
||||||
|
callback: async () => {
|
||||||
|
const file = this.app.workspace.getActiveFile();
|
||||||
|
if (!file) {
|
||||||
|
new Notice('No active file');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.addBookmark(file.path, file.basename, 'file');
|
||||||
|
new Notice(`Bookmarked: ${file.basename}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: 'waypoint-go-to-today',
|
||||||
|
name: 'Go to today\'s daily note',
|
||||||
|
callback: async () => {
|
||||||
|
await this.openPeriodNote('day', moment());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Events ──
|
||||||
|
|
||||||
|
this.registerEvent(
|
||||||
|
this.app.workspace.on('file-open', (file: TFile | null) => {
|
||||||
|
if (file) this.onFileOpen(file);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.registerEvent(
|
||||||
|
this.app.vault.on('create', () => this.onVaultChange()),
|
||||||
|
);
|
||||||
|
this.registerEvent(
|
||||||
|
this.app.vault.on('delete', () => this.onVaultChange()),
|
||||||
|
);
|
||||||
|
this.registerEvent(
|
||||||
|
this.app.vault.on('rename', (file, oldPath) => this.onRename(file, oldPath)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Auto-open view on first load
|
||||||
|
this.app.workspace.onLayoutReady(() => {
|
||||||
|
const leaves = this.app.workspace.getLeavesOfType(WAYPOINT_VIEW_TYPE);
|
||||||
|
if (leaves.length === 0) {
|
||||||
|
const leaf = this.app.workspace.getLeftLeaf(false);
|
||||||
|
if (leaf) {
|
||||||
|
leaf.setViewState({ type: WAYPOINT_VIEW_TYPE });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Midnight refresh (check every 10 min)
|
||||||
|
let lastDate = new Date().toDateString();
|
||||||
|
this.registerInterval(
|
||||||
|
window.setInterval(() => {
|
||||||
|
const currentDate = new Date().toDateString();
|
||||||
|
if (currentDate !== lastDate) {
|
||||||
|
lastDate = currentDate;
|
||||||
|
this.redrawAll();
|
||||||
|
}
|
||||||
|
}, 600000), // 10 min
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onunload(): Promise<void> {
|
||||||
|
this.app.workspace.detachLeavesOfType(WAYPOINT_VIEW_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Settings ──
|
||||||
|
|
||||||
|
async loadSettings(): Promise<void> {
|
||||||
|
const saved = await this.loadData() as Record<string, unknown> | null;
|
||||||
|
const s = (saved?.settings || {}) as Partial<WaypointSettings>;
|
||||||
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSettings(): Promise<void> {
|
||||||
|
const all = (await this.loadData()) as Record<string, unknown> || {};
|
||||||
|
all.settings = this.settings;
|
||||||
|
await this.saveData(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadWaypointData(): Promise<void> {
|
||||||
|
const saved = await this.loadData() as Record<string, unknown> | null;
|
||||||
|
const d = (saved?.waypointData || {}) as Partial<WaypointData>;
|
||||||
|
this.waypointData = Object.assign({}, DEFAULT_DATA, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveWaypointData(): Promise<void> {
|
||||||
|
const all = (await this.loadData()) as Record<string, unknown> || {};
|
||||||
|
all.waypointData = this.waypointData;
|
||||||
|
await this.saveData(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Recent Files ──
|
||||||
|
|
||||||
|
private onFileOpen(file: TFile): void {
|
||||||
|
if (this.settings.recentFiles.updateOn === 'file-edit') {
|
||||||
|
// We'll handle this via quick-preview in a future refinement
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.addToRecentFiles(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
addToRecentFiles(file: TFile): void {
|
||||||
|
this.recentFiles = this.recentFiles.filter(f => f.path !== file.path);
|
||||||
|
this.recentFiles.unshift({ path: file.path, basename: file.basename });
|
||||||
|
|
||||||
|
// Prune
|
||||||
|
if (this.recentFiles.length > this.settings.recentFiles.maxItems) {
|
||||||
|
this.recentFiles = this.recentFiles.slice(0, this.settings.recentFiles.maxItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.broadcastRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRename(file: TAbstractFile, oldPath: string): void {
|
||||||
|
const entry = this.recentFiles.find(f => f.path === oldPath);
|
||||||
|
if (entry) {
|
||||||
|
entry.path = file.path;
|
||||||
|
entry.basename = (file as TFile).basename || file.name.replace(/\.[^/.]+$/, '');
|
||||||
|
this.broadcastRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update bookmark file paths
|
||||||
|
this.updateBookmarkPath(oldPath, file.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onVaultChange(): void {
|
||||||
|
this.broadcastRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bookmarks ──
|
||||||
|
|
||||||
|
addBookmark(filePath: string, label: string, type: 'file' | 'group' | 'separator' | 'spacer' = 'file', icon = 'file'): BookmarkItem {
|
||||||
|
const id = `bm-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
||||||
|
const item: BookmarkItem = {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
label,
|
||||||
|
filePath: type === 'file' ? filePath : '',
|
||||||
|
icon,
|
||||||
|
children: [],
|
||||||
|
collapsed: false,
|
||||||
|
indent: 0,
|
||||||
|
};
|
||||||
|
this.waypointData.bookmarks.push(item);
|
||||||
|
this.saveWaypointData();
|
||||||
|
this.broadcastRedraw();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeBookmark(id: string): void {
|
||||||
|
const removeRecursive = (items: BookmarkItem[]): boolean => {
|
||||||
|
const idx = items.findIndex(i => i.id === id);
|
||||||
|
if (idx >= 0) {
|
||||||
|
items.splice(idx, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.children && removeRecursive(item.children)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
removeRecursive(this.waypointData.bookmarks);
|
||||||
|
this.saveWaypointData();
|
||||||
|
this.broadcastRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBookmark(id: string, updates: Partial<BookmarkItem>): void {
|
||||||
|
const findRecursive = (items: BookmarkItem[]): BookmarkItem | null => {
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.id === id) return item;
|
||||||
|
if (item.children) {
|
||||||
|
const found = findRecursive(item.children);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const item = findRecursive(this.waypointData.bookmarks);
|
||||||
|
if (item) {
|
||||||
|
Object.assign(item, updates);
|
||||||
|
this.saveWaypointData();
|
||||||
|
this.broadcastRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateBookmarkPath(oldPath: string, newPath: string): void {
|
||||||
|
const updateRecursive = (items: BookmarkItem[]) => {
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.filePath === oldPath) {
|
||||||
|
item.filePath = newPath;
|
||||||
|
}
|
||||||
|
if (item.children) updateRecursive(item.children);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateRecursive(this.waypointData.bookmarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Period note creation/opening ──
|
||||||
|
|
||||||
|
async openPeriodNote(period: 'day' | 'week' | 'month' | 'quarter' | 'year', date: moment.Moment): Promise<void> {
|
||||||
|
// Map period to settings and date format
|
||||||
|
type PeriodConfig = {
|
||||||
|
settings: PeriodNoteSettings;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const configs: Record<string, PeriodConfig> = {
|
||||||
|
day: { settings: this.settings.daily, label: 'Daily' },
|
||||||
|
week: { settings: this.settings.weekly, label: 'Weekly' },
|
||||||
|
month: { settings: this.settings.monthly, label: 'Monthly' },
|
||||||
|
quarter: { settings: this.settings.quarterly, label: 'Quarterly' },
|
||||||
|
year: { settings: this.settings.yearly, label: 'Yearly' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = configs[period];
|
||||||
|
if (!config) return;
|
||||||
|
|
||||||
|
const { settings: periodSettings } = config;
|
||||||
|
const filename = date.format(periodSettings.nameFormat) + '.md';
|
||||||
|
const fullPath = periodSettings.folder
|
||||||
|
? `${periodSettings.folder}/${filename}`
|
||||||
|
: filename;
|
||||||
|
|
||||||
|
// Check if exists
|
||||||
|
let file = this.app.vault.getFileByPath(fullPath);
|
||||||
|
if (!file) {
|
||||||
|
// Create it from template
|
||||||
|
try {
|
||||||
|
// Try to find template file
|
||||||
|
const templatePath = periodSettings.templateFile + '.md';
|
||||||
|
const templateFile = this.app.vault.getFileByPath(templatePath);
|
||||||
|
|
||||||
|
if (templateFile) {
|
||||||
|
const templateContent = await this.app.vault.read(templateFile);
|
||||||
|
file = await this.app.vault.create(fullPath, templateContent);
|
||||||
|
} else {
|
||||||
|
// Fallback: create with minimal frontmatter
|
||||||
|
const content = `---\ntype: ${periodSettings.typeProperty}\ndate: ${date.format('YYYY-MM-DD')}\n---\n\n`;
|
||||||
|
file = await this.app.vault.create(fullPath, content);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
new Notice(`Failed to create ${config.label.toLowerCase()} note: ${err.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new Notice(`Created ${config.label.toLowerCase()} note: ${filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const leaf = this.app.workspace.getLeaf(false);
|
||||||
|
await leaf.openFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Redraw ──
|
||||||
|
|
||||||
|
private redrawAll(): void {
|
||||||
|
this.broadcastRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
private broadcastRedraw(): void {
|
||||||
|
const leaves = this.app.workspace.getLeavesOfType(WAYPOINT_VIEW_TYPE);
|
||||||
|
for (const leaf of leaves) {
|
||||||
|
if (leaf.view instanceof WaypointView) {
|
||||||
|
leaf.view.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all markdown files that exist on a specific date.
|
||||||
|
* Used to show note indicators on the calendar.
|
||||||
|
*/
|
||||||
|
async getNotesForDate(dateStr: string): Promise<TFile[]> {
|
||||||
|
return this.app.vault.getFiles().filter(f =>
|
||||||
|
f.extension === 'md' && f.basename === dateStr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async hasNoteForDate(dateStr: string): Promise<boolean> {
|
||||||
|
return this.app.vault.getFiles().some(f =>
|
||||||
|
f.extension === 'md' && f.basename === dateStr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// ── Bookmark data model ──
|
||||||
|
|
||||||
|
export interface BookmarkItem {
|
||||||
|
id: string;
|
||||||
|
type: 'file' | 'group' | 'separator' | 'spacer';
|
||||||
|
label: string;
|
||||||
|
filePath: string;
|
||||||
|
icon: string;
|
||||||
|
children: BookmarkItem[];
|
||||||
|
collapsed: boolean;
|
||||||
|
indent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WaypointData {
|
||||||
|
bookmarks: BookmarkItem[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import { Setting, PluginSettingTab, App, Plugin } from 'obsidian';
|
||||||
|
import { WaypointSettings, DEFAULT_SETTINGS, PeriodNoteSettings } from 'src/settings';
|
||||||
|
|
||||||
|
export class WaypointSettingTab extends PluginSettingTab {
|
||||||
|
private plugin: Plugin;
|
||||||
|
private settings: WaypointSettings;
|
||||||
|
private onSettingsChange: () => void;
|
||||||
|
|
||||||
|
constructor(app: App, plugin: Plugin, settings: WaypointSettings, onSettingsChange: () => void) {
|
||||||
|
super(app, plugin);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.settings = settings;
|
||||||
|
this.onSettingsChange = onSettingsChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
display(): void {
|
||||||
|
const { containerEl } = this;
|
||||||
|
containerEl.empty();
|
||||||
|
|
||||||
|
// ── Calendar Section ──
|
||||||
|
new Setting(containerEl).setHeading().setName('Calendar');
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('First day of week')
|
||||||
|
.setDesc('Which day the calendar week starts on.')
|
||||||
|
.addDropdown((dropdown) => {
|
||||||
|
dropdown
|
||||||
|
.addOption('0', 'Sunday')
|
||||||
|
.addOption('1', 'Monday')
|
||||||
|
.setValue(String(this.settings.calendar.firstDayOfWeek))
|
||||||
|
.onChange((value) => {
|
||||||
|
this.settings.calendar.firstDayOfWeek = parseInt(value, 10);
|
||||||
|
this.saveAndRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Show note indicators')
|
||||||
|
.setDesc('Show a dot on days that have existing notes.')
|
||||||
|
.addToggle((toggle) => {
|
||||||
|
toggle
|
||||||
|
.setValue(this.settings.calendar.showNoteIndicators)
|
||||||
|
.onChange((value) => {
|
||||||
|
this.settings.calendar.showNoteIndicators = value;
|
||||||
|
this.saveAndRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Periodic Note Settings ──
|
||||||
|
new Setting(containerEl).setHeading().setName('Periodic Note Paths');
|
||||||
|
|
||||||
|
this.addPeriodNoteSettings('Daily', this.settings.daily);
|
||||||
|
this.addPeriodNoteSettings('Weekly', this.settings.weekly);
|
||||||
|
this.addPeriodNoteSettings('Monthly', this.settings.monthly);
|
||||||
|
this.addPeriodNoteSettings('Quarterly', this.settings.quarterly);
|
||||||
|
this.addPeriodNoteSettings('Yearly', this.settings.yearly);
|
||||||
|
|
||||||
|
// ── Recent Files Section ──
|
||||||
|
new Setting(containerEl).setHeading().setName('Recent Files');
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Max items')
|
||||||
|
.setDesc('Maximum number of recent files to track.')
|
||||||
|
.addText((text) => {
|
||||||
|
text.inputEl.setAttr('type', 'number');
|
||||||
|
text.inputEl.setAttr('placeholder', '50');
|
||||||
|
text.setValue(String(this.settings.recentFiles.maxItems));
|
||||||
|
text.inputEl.onblur = () => {
|
||||||
|
const val = parseInt(text.getValue(), 10);
|
||||||
|
if (!isNaN(val) && val > 0) {
|
||||||
|
this.settings.recentFiles.maxItems = val;
|
||||||
|
this.saveAndRefresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Update on')
|
||||||
|
.setDesc('When to add a file to the recent list.')
|
||||||
|
.addDropdown((dropdown) => {
|
||||||
|
dropdown
|
||||||
|
.addOption('file-open', 'File opened')
|
||||||
|
.addOption('file-edit', 'File changed')
|
||||||
|
.setValue(this.settings.recentFiles.updateOn)
|
||||||
|
.onChange((value: 'file-open' | 'file-edit') => {
|
||||||
|
this.settings.recentFiles.updateOn = value;
|
||||||
|
this.saveAndRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const pathDesc = new DocumentFragment();
|
||||||
|
pathDesc.appendText('Regex patterns for paths to exclude. One per line.');
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Omitted paths')
|
||||||
|
.setDesc(pathDesc)
|
||||||
|
.addTextArea((text) => {
|
||||||
|
text.inputEl.setAttr('rows', 4);
|
||||||
|
text.setPlaceholder('^archives/\n\\.png$');
|
||||||
|
text.setValue(this.settings.recentFiles.omittedPaths.join('\n'));
|
||||||
|
text.inputEl.onblur = () => {
|
||||||
|
this.settings.recentFiles.omittedPaths = text.getValue().split('\n').filter(p => p.trim());
|
||||||
|
this.saveAndRefresh();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const tagDesc = new DocumentFragment();
|
||||||
|
tagDesc.appendText('Regex patterns for frontmatter tags to exclude. One per line.');
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Omitted tags')
|
||||||
|
.setDesc(tagDesc)
|
||||||
|
.addTextArea((text) => {
|
||||||
|
text.inputEl.setAttr('rows', 4);
|
||||||
|
text.setPlaceholder('ignore\narchive');
|
||||||
|
text.setValue(this.settings.recentFiles.omittedTags.join('\n'));
|
||||||
|
text.inputEl.onblur = () => {
|
||||||
|
this.settings.recentFiles.omittedTags = text.getValue().split('\n').filter(t => t.trim());
|
||||||
|
this.saveAndRefresh();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private addPeriodNoteSettings(label: string, period: PeriodNoteSettings): void {
|
||||||
|
new Setting(this.containerEl).setHeading().setName(label);
|
||||||
|
|
||||||
|
new Setting(this.containerEl)
|
||||||
|
.setName('Folder')
|
||||||
|
.setDesc(`Folder path for ${label.toLowerCase()} notes.`)
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder('periodic/daily');
|
||||||
|
text.setValue(period.folder);
|
||||||
|
text.onChange((value) => {
|
||||||
|
period.folder = value;
|
||||||
|
this.saveAndRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(this.containerEl)
|
||||||
|
.setName('Name format')
|
||||||
|
.setDesc(`Date format for ${label.toLowerCase()} note filenames (moment.js format).`)
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder('yyyy-MM-dd');
|
||||||
|
text.setValue(period.nameFormat);
|
||||||
|
text.onChange((value) => {
|
||||||
|
period.nameFormat = value;
|
||||||
|
this.saveAndRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(this.containerEl)
|
||||||
|
.setName('Template file')
|
||||||
|
.setDesc(`Path to the template file (without .md extension).`)
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder('Templates/Daily note');
|
||||||
|
text.setValue(period.templateFile);
|
||||||
|
text.onChange((value) => {
|
||||||
|
period.templateFile = value;
|
||||||
|
this.saveAndRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(this.containerEl)
|
||||||
|
.setName('Type property')
|
||||||
|
.setDesc(`Value for the 'type' frontmatter property.`)
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder('daily-note');
|
||||||
|
text.setValue(period.typeProperty);
|
||||||
|
text.onChange((value) => {
|
||||||
|
period.typeProperty = value;
|
||||||
|
this.saveAndRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveAndRefresh(): Promise<void> {
|
||||||
|
await this.plugin.saveData(this.settings);
|
||||||
|
this.onSettingsChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// ── Waypoint Settings ──
|
||||||
|
|
||||||
|
export interface PeriodNoteSettings {
|
||||||
|
folder: string;
|
||||||
|
templateFile: string;
|
||||||
|
nameFormat: string;
|
||||||
|
typeProperty: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalendarSettings {
|
||||||
|
firstDayOfWeek: number; // 0=Sunday, 1=Monday
|
||||||
|
showNoteIndicators: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecentFilesSettings {
|
||||||
|
maxItems: number;
|
||||||
|
updateOn: 'file-open' | 'file-edit';
|
||||||
|
omittedPaths: string[];
|
||||||
|
omittedTags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WaypointSettings {
|
||||||
|
calendar: CalendarSettings;
|
||||||
|
daily: PeriodNoteSettings;
|
||||||
|
weekly: PeriodNoteSettings;
|
||||||
|
monthly: PeriodNoteSettings;
|
||||||
|
quarterly: PeriodNoteSettings;
|
||||||
|
yearly: PeriodNoteSettings;
|
||||||
|
recentFiles: RecentFilesSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_SETTINGS: WaypointSettings = {
|
||||||
|
calendar: {
|
||||||
|
firstDayOfWeek: 1, // Monday
|
||||||
|
showNoteIndicators: true,
|
||||||
|
},
|
||||||
|
daily: {
|
||||||
|
folder: 'periodic/daily',
|
||||||
|
templateFile: 'Templates/Daily note',
|
||||||
|
nameFormat: 'YYYY-MM-DD',
|
||||||
|
typeProperty: 'daily-note',
|
||||||
|
},
|
||||||
|
weekly: {
|
||||||
|
folder: 'periodic/weekly',
|
||||||
|
templateFile: 'Templates/Weekly note',
|
||||||
|
nameFormat: 'GGGG-[W]WW',
|
||||||
|
typeProperty: 'weekly-note',
|
||||||
|
},
|
||||||
|
monthly: {
|
||||||
|
folder: 'periodic/monthly',
|
||||||
|
templateFile: 'Templates/Monthly note',
|
||||||
|
nameFormat: 'YYYY-MM',
|
||||||
|
typeProperty: 'monthly-note',
|
||||||
|
},
|
||||||
|
quarterly: {
|
||||||
|
folder: 'periodic/quarterly',
|
||||||
|
templateFile: 'Templates/Quarterly note',
|
||||||
|
nameFormat: 'YYYY-[Q]Q',
|
||||||
|
typeProperty: 'quarterly-note',
|
||||||
|
},
|
||||||
|
yearly: {
|
||||||
|
folder: 'periodic/yearly',
|
||||||
|
templateFile: 'Templates/Yearly note',
|
||||||
|
nameFormat: 'YYYY',
|
||||||
|
typeProperty: 'yearly-note',
|
||||||
|
},
|
||||||
|
recentFiles: {
|
||||||
|
maxItems: 50,
|
||||||
|
updateOn: 'file-open',
|
||||||
|
omittedPaths: [],
|
||||||
|
omittedTags: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// ── Date utilities ──
|
||||||
|
// Uses moment.js which is bundled with Obsidian.
|
||||||
|
// Period format helpers following O-vault conventions.
|
||||||
|
|
||||||
|
import { moment } from 'obsidian';
|
||||||
|
|
||||||
|
export interface PeriodInfo {
|
||||||
|
day: string; // yyyy-MM-dd
|
||||||
|
week: string; // GGGG-[W]WW
|
||||||
|
month: string; // yyyy-MM
|
||||||
|
quarter: string; // yyyy-[Q]Q
|
||||||
|
year: string; // yyyy
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalendarDay {
|
||||||
|
date: moment.Moment;
|
||||||
|
dayOfMonth: number;
|
||||||
|
isToday: boolean;
|
||||||
|
isCurrentMonth: boolean;
|
||||||
|
isoWeekNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalendarWeek {
|
||||||
|
weekNumber: number;
|
||||||
|
days: CalendarDay[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get period info for a given date.
|
||||||
|
*/
|
||||||
|
export function getPeriodInfo(date: moment.Moment): PeriodInfo {
|
||||||
|
return {
|
||||||
|
day: date.format('YYYY-MM-DD'),
|
||||||
|
week: date.format('GGGG-[W]WW'),
|
||||||
|
month: date.format('YYYY-MM'),
|
||||||
|
quarter: date.format('YYYY-[Q]Q'),
|
||||||
|
year: date.format('YYYY'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get quarter string from a moment date (e.g. "2026-Q3").
|
||||||
|
*/
|
||||||
|
export function getQuarterString(date: moment.Moment): string {
|
||||||
|
return date.format('YYYY-[Q]Q');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current date's period info.
|
||||||
|
*/
|
||||||
|
export function getTodayPeriod(): PeriodInfo {
|
||||||
|
return getPeriodInfo(moment());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a month grid as an array of weeks.
|
||||||
|
* Each week has 7 days, starting on the configured first day of week.
|
||||||
|
* ISO week numbers are calculated from the Monday of that week row.
|
||||||
|
*/
|
||||||
|
export function getMonthGrid(
|
||||||
|
year: number,
|
||||||
|
month: number, // 0-indexed (0 = January)
|
||||||
|
firstDayOfWeek: number, // 0 = Sunday, 1 = Monday
|
||||||
|
): CalendarWeek[] {
|
||||||
|
const firstOfMonth = moment({ year, month, day: 1 });
|
||||||
|
const lastOfMonth = moment(firstOfMonth).endOf('month');
|
||||||
|
|
||||||
|
// Find the first day to display (may go back into previous month)
|
||||||
|
const startDate = moment(firstOfMonth)
|
||||||
|
.subtract((firstOfMonth.day() - firstDayOfWeek + 7) % 7, 'days');
|
||||||
|
|
||||||
|
const today = moment().startOf('day');
|
||||||
|
const weeks: CalendarWeek[] = [];
|
||||||
|
let currentDay = moment(startDate);
|
||||||
|
|
||||||
|
while (currentDay.isBefore(lastOfMonth) || currentDay.month() === month) {
|
||||||
|
const weekDays: CalendarDay[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
weekDays.push({
|
||||||
|
date: moment(currentDay),
|
||||||
|
dayOfMonth: currentDay.date(),
|
||||||
|
isToday: currentDay.isSame(today, 'day'),
|
||||||
|
isCurrentMonth: currentDay.month() === month,
|
||||||
|
isoWeekNumber: currentDay.isoWeek(),
|
||||||
|
});
|
||||||
|
currentDay.add(1, 'day');
|
||||||
|
}
|
||||||
|
|
||||||
|
weeks.push({
|
||||||
|
weekNumber: weekDays[0].isoWeekNumber,
|
||||||
|
days: weekDays,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Safety: break after 6 weeks
|
||||||
|
if (weeks.length >= 6) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return weeks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date label for the breadcrumb (e.g. "June 1st, 2026").
|
||||||
|
*/
|
||||||
|
export function formatDateLabel(date: moment.Moment): string {
|
||||||
|
return date.format('MMMM Do, YYYY');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a month label (e.g. "June 2026").
|
||||||
|
*/
|
||||||
|
export function formatMonthLabel(date: moment.Moment): string {
|
||||||
|
return date.format('MMMM YYYY');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get readable quarter label (e.g. "Q3 2026").
|
||||||
|
*/
|
||||||
|
export function formatQuarterLabel(date: moment.Moment): string {
|
||||||
|
return date.format('[Q]Q YYYY');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate a date by a given period and amount.
|
||||||
|
*/
|
||||||
|
export function navigateDate(
|
||||||
|
date: moment.Moment,
|
||||||
|
period: 'day' | 'week' | 'month' | 'quarter' | 'year',
|
||||||
|
delta: number,
|
||||||
|
): moment.Moment {
|
||||||
|
if (period === 'quarter') {
|
||||||
|
return moment(date).add(delta * 3, 'months');
|
||||||
|
}
|
||||||
|
return moment(date).add(delta, period);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a note filename from a date using the given format string.
|
||||||
|
*/
|
||||||
|
export function formatPeriodName(date: moment.Moment, format: string): string {
|
||||||
|
return date.format(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ISO week number for a given date.
|
||||||
|
*/
|
||||||
|
export function getISOWeek(date: moment.Moment): number {
|
||||||
|
return date.isoWeek();
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
+294
@@ -0,0 +1,294 @@
|
|||||||
|
/* ── Waypoint Base ── */
|
||||||
|
|
||||||
|
.waypoint-view {
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-view .waypoint-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-view .waypoint-section:last-child {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-view .waypoint-section-header {
|
||||||
|
font-size: var(--font-ui-small);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Calendar ── */
|
||||||
|
|
||||||
|
.waypoint-calendar {
|
||||||
|
font-size: var(--font-ui-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar-breadcrumb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: var(--font-ui-medium);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar-breadcrumb .waypoint-clickable {
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: var(--cursor);
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar-breadcrumb .waypoint-clickable:hover {
|
||||||
|
color: var(--text-accent);
|
||||||
|
background-color: var(--background-modifier-active-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-separator {
|
||||||
|
color: var(--text-faint);
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar-today-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar-today-group button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: var(--cursor);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-ui-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar-today-group button:hover {
|
||||||
|
color: var(--text-accent);
|
||||||
|
background-color: var(--background-modifier-active-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar-today-group .waypoint-calendar-today-btn {
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar table {
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar th,
|
||||||
|
.waypoint-calendar td {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2px 1px;
|
||||||
|
font-size: var(--font-ui-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar th {
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
color: var(--text-faint);
|
||||||
|
font-size: calc(var(--font-ui-small) * 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-weeknum {
|
||||||
|
font-size: calc(var(--font-ui-small) * 0.75);
|
||||||
|
color: var(--text-faint);
|
||||||
|
font-weight: var(--font-light);
|
||||||
|
cursor: var(--cursor);
|
||||||
|
padding: 2px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-weeknum:hover {
|
||||||
|
color: var(--text-accent);
|
||||||
|
background-color: var(--background-modifier-active-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-day {
|
||||||
|
cursor: var(--cursor);
|
||||||
|
padding: 3px 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-day:hover {
|
||||||
|
background-color: var(--background-modifier-active-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-day.other-month {
|
||||||
|
opacity: 0.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-day.today {
|
||||||
|
color: var(--text-accent);
|
||||||
|
border: 1px solid var(--text-accent);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-day.has-note::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--text-faint);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-calendar .waypoint-day.today.has-note::after {
|
||||||
|
background-color: var(--text-accent);
|
||||||
|
}
|
||||||
|
/* ── Recent Files ── */
|
||||||
|
|
||||||
|
.waypoint-recent-files .nav-file {
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-recent-files .nav-file-title {
|
||||||
|
padding: 2px 28px 2px 4px;
|
||||||
|
font-size: var(--font-ui-small);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-recent-files .waypoint-recent-remove {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
opacity: 0;
|
||||||
|
cursor: var(--cursor);
|
||||||
|
color: var(--text-faint);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-recent-files .nav-file-title:hover .waypoint-recent-remove {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-recent-files .waypoint-recent-remove:hover {
|
||||||
|
color: var(--text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Favorites ── */
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 8px 2px 4px;
|
||||||
|
font-size: var(--font-ui-small);
|
||||||
|
cursor: var(--cursor);
|
||||||
|
border-radius: 4px;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-item:hover {
|
||||||
|
background-color: var(--background-modifier-active-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-item .waypoint-bm-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-item .waypoint-bm-label {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-group {
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-group .waypoint-bm-chevron {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: transform 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-group.collapsed .waypoint-bm-chevron {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-children {
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-children.collapsed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Drag-and-drop ── */
|
||||||
|
|
||||||
|
.waypoint-bm-dragging {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bm-drop-line {
|
||||||
|
border-top: 3px solid var(--text-accent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bm-drop-line.waypoint-bm-drop-below {
|
||||||
|
border-top: none !important;
|
||||||
|
border-bottom: 3px solid var(--text-accent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Separator & Spacer ── */
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-item.waypoint-bookmark-separator {
|
||||||
|
padding: 0;
|
||||||
|
min-height: auto;
|
||||||
|
height: auto;
|
||||||
|
border: none;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-item.waypoint-bookmark-separator::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
margin: 6px 8px;
|
||||||
|
background: var(--background-modifier-border);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waypoint-favorites .waypoint-bookmark-item.waypoint-bookmark-spacer {
|
||||||
|
padding: 0;
|
||||||
|
min-height: auto;
|
||||||
|
height: 14px;
|
||||||
|
border: none;
|
||||||
|
cursor: grab;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
- [ ] when user middle clicks on a bookmark, it should open in a new tab
|
||||||
|
- [ ] the sidebar should never scroll. the content of recent files should wrap after 10 files or so (no sidebar visible)
|
||||||
|
- [ ] it should be possible to add a separator line and/or an empty space
|
||||||
|
- [ ] by default, the bookmark should have no icon
|
||||||
|
- [ ]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"inlineSourceMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ES6",
|
||||||
|
"allowJs": false,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": false,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"lib": [
|
||||||
|
"DOM",
|
||||||
|
"ES5",
|
||||||
|
"ES6",
|
||||||
|
"ES7"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"src/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user