diff --git a/docs/ABSTRACTIONS.md b/docs/ABSTRACTIONS.md index 6b271770..02943e2d 100644 --- a/docs/ABSTRACTIONS.md +++ b/docs/ABSTRACTIONS.md @@ -39,9 +39,8 @@ Any frontmatter field whose name starts with `_` is a **system property**: Examples: ```yaml _pinned_properties: # which properties appear in the editor inline bar (per-type) - - "Status:circle-dot" # format: "key:icon" (icon is optional) - - "Belongs to:arrow-up-right" - - "Due date:calendar" + - key: status + icon: circle-dot _icon: shapes # icon assigned to a type _color: blue # color assigned to a type _order: 10 # sort order in the sidebar @@ -179,7 +178,6 @@ Each entity type can have a corresponding **type document** in the `type/` folde | `sort` | string | Default sort: "modified:desc", "title:asc", "property:Priority:asc" | | `view` | string | Default view mode: "all", "editor-list", "editor-only" | | `visible` | bool | Whether type appears in sidebar (default: true) | -| `_pinned_properties` | list | Pinned properties shown in editor bar + note list. Each item: `"key:icon"` | **Type relationship**: When any entry has an `isA` value (e.g., "Project"), the Rust backend automatically adds a `"Type"` entry to its `relationships` map pointing to `[[type/project]]`. This makes the type navigable from the Inspector panel. diff --git a/src-tauri/src/vault/cache.rs b/src-tauri/src/vault/cache.rs index 9a682cc7..55f85ef7 100644 --- a/src-tauri/src/vault/cache.rs +++ b/src-tauri/src/vault/cache.rs @@ -8,7 +8,7 @@ use super::{parse_md_file, scan_vault, VaultEntry}; // --- Vault Cache --- /// Bump this when VaultEntry fields change to force a full rescan. -const CACHE_VERSION: u32 = 9; +const CACHE_VERSION: u32 = 8; #[derive(Debug, Serialize, Deserialize)] struct VaultCache { diff --git a/src-tauri/src/vault/entry.rs b/src-tauri/src/vault/entry.rs index 3f658c6c..fa545456 100644 --- a/src-tauri/src/vault/entry.rs +++ b/src-tauri/src/vault/entry.rs @@ -1,13 +1,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -/// A single pinned-property config item stored in the type's frontmatter. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub struct PinnedPropertyConfig { - pub key: String, - pub icon: Option, -} - #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct VaultEntry { pub path: String, @@ -66,7 +59,4 @@ pub struct VaultEntry { /// Only includes strings, numbers, and booleans — arrays/objects are excluded. #[serde(default)] pub properties: HashMap, - /// Pinned properties configuration for Type entries. - #[serde(rename = "pinnedProperties", default)] - pub pinned_properties: Vec, } diff --git a/src-tauri/src/vault/frontmatter.rs b/src-tauri/src/vault/frontmatter.rs index cfba1bf2..448e29e5 100644 --- a/src-tauri/src/vault/frontmatter.rs +++ b/src-tauri/src/vault/frontmatter.rs @@ -1,4 +1,3 @@ -use crate::vault::entry::PinnedPropertyConfig; use crate::vault::parsing::contains_wikilink; use serde::Deserialize; use std::collections::HashMap; @@ -194,9 +193,6 @@ pub(crate) fn extract_relationships( let mut relationships = HashMap::new(); for (key, value) in data { - if key.starts_with('_') { - continue; - } if SKIP_KEYS.iter().any(|k| k.eq_ignore_ascii_case(key)) { continue; } @@ -232,9 +228,6 @@ pub(crate) fn extract_properties( let mut properties = HashMap::new(); for (key, value) in data { - if key.starts_with('_') { - continue; - } let lower = key.to_ascii_lowercase(); if SKIP_KEYS.iter().any(|k| k.eq_ignore_ascii_case(&lower)) { continue; @@ -272,38 +265,6 @@ pub(crate) fn resolve_is_a(fm_is_a: Option) -> Option { fm_is_a.and_then(|a| a.into_vec().into_iter().next()) } -/// Parse a single pinned-property entry from "key:icon" format. -fn parse_pinned_entry(s: &str) -> PinnedPropertyConfig { - match s.split_once(':') { - Some((key, icon)) if !icon.is_empty() => PinnedPropertyConfig { - key: key.trim().to_string(), - icon: Some(icon.trim().to_string()), - }, - _ => PinnedPropertyConfig { - key: s.trim().to_string(), - icon: None, - }, - } -} - -/// Extract `_pinned_properties` from raw YAML frontmatter. -pub(crate) fn extract_pinned_properties( - data: &HashMap, -) -> Vec { - let value = match data.get("_pinned_properties") { - Some(v) => v, - None => return Vec::new(), - }; - match value { - serde_json::Value::Array(arr) => arr - .iter() - .filter_map(|v| v.as_str()) - .map(parse_pinned_entry) - .collect(), - _ => Vec::new(), - } -} - /// Convert gray_matter::Pod to serde_json::Value fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value { match pod { @@ -323,26 +284,17 @@ fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value { } } -/// Return type for `extract_fm_and_rels`: (frontmatter, relationships, properties, pinned). -type FrontmatterBundle = ( +/// Extract frontmatter, relationships, and custom properties from parsed gray_matter data. +pub(crate) fn extract_fm_and_rels( + data: Option, +) -> ( Frontmatter, HashMap>, HashMap, - Vec, -); - -/// Extract frontmatter, relationships, custom properties, and pinned-property config. -pub(crate) fn extract_fm_and_rels(data: Option) -> FrontmatterBundle { +) { let hash = match data { Some(gray_matter::Pod::Hash(map)) => map, - _ => { - return ( - Frontmatter::default(), - HashMap::new(), - HashMap::new(), - Vec::new(), - ) - } + _ => return (Frontmatter::default(), HashMap::new(), HashMap::new()), }; let json_map: HashMap = hash.into_iter().map(|(k, v)| (k, pod_to_json(v))).collect(); @@ -350,70 +302,5 @@ pub(crate) fn extract_fm_and_rels(data: Option) -> Frontmatter parse_frontmatter(&json_map), extract_relationships(&json_map), extract_properties(&json_map), - extract_pinned_properties(&json_map), ) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_pinned_entry_with_icon() { - let result = parse_pinned_entry("Status:circle-dot"); - assert_eq!(result.key, "Status"); - assert_eq!(result.icon, Some("circle-dot".to_string())); - } - - #[test] - fn test_parse_pinned_entry_without_icon() { - let result = parse_pinned_entry("Priority"); - assert_eq!(result.key, "Priority"); - assert_eq!(result.icon, None); - } - - #[test] - fn test_extract_pinned_properties_array() { - let mut data = HashMap::new(); - data.insert( - "_pinned_properties".to_string(), - serde_json::json!(["Status:circle-dot", "Belongs to:arrow-up-right", "date"]), - ); - let result = extract_pinned_properties(&data); - assert_eq!(result.len(), 3); - assert_eq!(result[0].key, "Status"); - assert_eq!(result[0].icon, Some("circle-dot".to_string())); - assert_eq!(result[1].key, "Belongs to"); - assert_eq!(result[1].icon, Some("arrow-up-right".to_string())); - assert_eq!(result[2].key, "date"); - assert_eq!(result[2].icon, None); - } - - #[test] - fn test_extract_pinned_properties_missing() { - let data = HashMap::new(); - assert!(extract_pinned_properties(&data).is_empty()); - } - - #[test] - fn test_underscore_keys_excluded_from_properties() { - let mut data = HashMap::new(); - data.insert("_pinned_properties".to_string(), serde_json::json!(["a:b"])); - data.insert("_hidden".to_string(), serde_json::json!("secret")); - data.insert("visible_key".to_string(), serde_json::json!("value")); - let props = extract_properties(&data); - assert!(!props.contains_key("_pinned_properties")); - assert!(!props.contains_key("_hidden")); - assert!(props.contains_key("visible_key")); - } - - #[test] - fn test_underscore_keys_excluded_from_relationships() { - let mut data = HashMap::new(); - data.insert("_refs".to_string(), serde_json::json!("[[note]]")); - data.insert("Topics".to_string(), serde_json::json!("[[topic]]")); - let rels = extract_relationships(&data); - assert!(!rels.contains_key("_refs")); - assert!(rels.contains_key("Topics")); - } -} diff --git a/src-tauri/src/vault/mod.rs b/src-tauri/src/vault/mod.rs index 892c5de5..537398c5 100644 --- a/src-tauri/src/vault/mod.rs +++ b/src-tauri/src/vault/mod.rs @@ -43,8 +43,7 @@ pub fn parse_md_file(path: &Path) -> Result { let matter = Matter::::new(); let parsed = matter.parse(&content); - let (frontmatter, mut relationships, properties, pinned_properties) = - extract_fm_and_rels(parsed.data); + let (frontmatter, mut relationships, properties) = extract_fm_and_rels(parsed.data); let title = extract_title(frontmatter.title.as_deref(), &content, &filename); let snippet = extract_snippet(&content); @@ -104,7 +103,6 @@ pub fn parse_md_file(path: &Path) -> Result { word_count, outgoing_links, properties, - pinned_properties, }) } diff --git a/src/components/DynamicPropertiesPanel.tsx b/src/components/DynamicPropertiesPanel.tsx index eb74f43f..67e12da8 100644 --- a/src/components/DynamicPropertiesPanel.tsx +++ b/src/components/DynamicPropertiesPanel.tsx @@ -1,4 +1,3 @@ -import { useState, useCallback, useRef, useEffect } from 'react' import type { VaultEntry } from '../types' import type { FrontmatterValue } from './Inspector' import type { ParsedFrontmatter } from '../utils/frontmatter' @@ -9,7 +8,6 @@ import { TypeSelector } from './TypeSelector' import { AddPropertyForm } from './AddPropertyForm' import { countWords } from '../utils/wikilinks' import type { PropertyDisplayMode } from '../utils/propertyTypes' -import { PushPin } from '@phosphor-icons/react' // eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component export function containsWikilinks(value: FrontmatterValue): boolean { @@ -32,51 +30,15 @@ function formatFileSize(bytes: number): string { return `${mb.toFixed(1)} MB` } -function PropertyPinMenu({ x, y, isPinned, onPin, onUnpin, onClose }: { - x: number; y: number; isPinned: boolean - onPin: () => void; onUnpin: () => void; onClose: () => void -}) { - const ref = useRef(null) - useEffect(() => { - const handler = (e: MouseEvent) => { - if (ref.current && !ref.current.contains(e.target as Node)) onClose() - } - document.addEventListener('mousedown', handler) - return () => document.removeEventListener('mousedown', handler) - }, [onClose]) - - return ( -
- -
- ) -} - -function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultStatuses, vaultTags, isPinned, onStartEdit, onSave, onSaveList, onUpdate, onDelete, onDisplayModeChange, onPin, onUnpin }: { +function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultStatuses, vaultTags, onStartEdit, onSave, onSaveList, onUpdate, onDelete, onDisplayModeChange }: { propKey: string; value: FrontmatterValue; editingKey: string | null displayMode: PropertyDisplayMode; autoMode: PropertyDisplayMode vaultStatuses: string[]; vaultTags: string[] - isPinned: boolean onStartEdit: (key: string | null) => void; onSave: (key: string, value: string) => void onSaveList: (key: string, items: string[]) => void onUpdate?: (key: string, value: FrontmatterValue) => void; onDelete?: (key: string) => void onDisplayModeChange: (key: string, mode: PropertyDisplayMode | null) => void - onPin?: (key: string) => void; onUnpin?: (key: string) => void }) { - const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number } | null>(null) - const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && editingKey !== propKey) { e.preventDefault() @@ -84,21 +46,9 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS } } - const handleContextMenu = useCallback((e: React.MouseEvent) => { - if (!onPin && !onUnpin) return - e.preventDefault() - setCtxMenu({ x: e.clientX, y: e.clientY }) - }, [onPin, onUnpin]) - return ( -
+
- {isPinned && } {propKey} {onDelete && ( @@ -108,13 +58,6 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
- {ctxMenu && ( - onPin?.(propKey)} onUnpin={() => onUnpin?.(propKey)} - onClose={() => setCtxMenu(null)} - /> - )}
) } @@ -155,7 +98,6 @@ function NoteInfoSection({ entry, wordCount }: { entry: VaultEntry; wordCount: n export function DynamicPropertiesPanel({ entry, content, frontmatter, entries, onUpdateProperty, onDeleteProperty, onAddProperty, onNavigate, - isPinned, onPin, onUnpin, }: { entry: VaultEntry content: string | null @@ -165,9 +107,6 @@ export function DynamicPropertiesPanel({ onDeleteProperty?: (key: string) => void onAddProperty?: (key: string, value: FrontmatterValue) => void onNavigate?: (target: string) => void - isPinned?: (key: string) => boolean - onPin?: (key: string) => void - onUnpin?: (key: string) => void }) { const { editingKey, setEditingKey, showAddDialog, setShowAddDialog, displayOverrides, @@ -187,12 +126,10 @@ export function DynamicPropertiesPanel({ editingKey={editingKey} displayMode={getEffectiveDisplayMode(key, value, displayOverrides)} autoMode={detectPropertyType(key, value)} vaultStatuses={vaultStatuses} vaultTags={vaultTagsByKey[key] ?? []} - isPinned={isPinned?.(key) ?? false} onStartEdit={setEditingKey} onSave={handleSaveValue} onSaveList={handleSaveList} onUpdate={onUpdateProperty} onDelete={onDeleteProperty} onDisplayModeChange={handleDisplayModeChange} - onPin={onPin} onUnpin={onUnpin} /> ))}
diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 4be7ca95..c427b717 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -264,7 +264,6 @@ export const Editor = memo(function Editor(props: EditorProps) { isConflicted={isConflicted} onKeepMine={onKeepMine} onKeepTheirs={onKeepTheirs} - onUpdateFrontmatter={onUpdateFrontmatter} /> } {(showAIChat || !inspectorCollapsed) && } diff --git a/src/components/EditorContent.tsx b/src/components/EditorContent.tsx index 4882f3d7..afc1e5b2 100644 --- a/src/components/EditorContent.tsx +++ b/src/components/EditorContent.tsx @@ -1,8 +1,7 @@ import type React from 'react' -import { useCallback, useMemo } from 'react' +import { useCallback } from 'react' import type { VaultEntry, NoteStatus } from '../types' import type { useCreateBlockNote } from '@blocknote/react' -import type { FrontmatterValue } from './Inspector' import { DiffView } from './DiffView' import { BreadcrumbBar } from './BreadcrumbBar' import { TitleField } from './TitleField' @@ -14,8 +13,6 @@ import { RawEditorView } from './RawEditorView' import { countWords } from '../utils/wikilinks' import { SingleEditorView } from './SingleEditorView' import { isEmoji } from '../utils/emoji' -import { parseFrontmatter } from '../utils/frontmatter' -import { PinnedPropertiesBar } from './PinnedPropertiesBar' interface Tab { entry: VaultEntry @@ -63,8 +60,6 @@ interface EditorContentProps { onKeepMine?: (path: string) => void /** Resolve conflict by keeping the remote version. */ onKeepTheirs?: (path: string) => void - /** Update a frontmatter property (for inline pinned-property editing). */ - onUpdateFrontmatter?: (path: string, key: string, value: FrontmatterValue) => Promise } function EditorLoadingSkeleton() { @@ -161,7 +156,6 @@ export function EditorContent({ onDeleteNote, rawLatestContentRef, onTitleChange, onSetNoteIcon, onRemoveNoteIcon, isConflicted, onKeepMine, onKeepTheirs, - onUpdateFrontmatter, ...breadcrumbProps }: EditorContentProps) { // Look up trashed/archived from the latest vault entries, not the tab snapshot, @@ -181,12 +175,6 @@ export function EditorContent({ if (activeTab) onRemoveNoteIcon?.(activeTab.entry.path) }, [activeTab, onRemoveNoteIcon]) - const frontmatter = useMemo( - () => parseFrontmatter(activeTab?.content ?? null), - [activeTab?.content], - ) - const currentEntry = freshEntry ?? activeTab?.entry ?? null - return (
{activeTab && ( @@ -227,15 +215,6 @@ export function EditorContent({ editable={!isTrashed} onTitleChange={(newTitle) => onTitleChange?.(activeTab.entry.path, newTitle)} /> - {currentEntry && ( - - )}
diff --git a/src/components/Inspector.tsx b/src/components/Inspector.tsx index d3b50c83..2b397fcb 100644 --- a/src/components/Inspector.tsx +++ b/src/components/Inspector.tsx @@ -8,7 +8,6 @@ import { DynamicPropertiesPanel } from './DynamicPropertiesPanel' import { DynamicRelationshipsPanel, BacklinksPanel, ReferencedByPanel, GitHistoryPanel, InstancesPanel } from './InspectorPanels' import { wikilinkTarget } from '../utils/wikilink' import type { ReferencedByItem, BacklinkItem } from './InspectorPanels' -import { usePinnedProperties } from '../hooks/usePinnedProperties' export type FrontmatterValue = string | number | boolean | string[] | null @@ -139,11 +138,6 @@ export function Inspector({ if (entry && onAddProperty) onAddProperty(entry.path, key, value) }, [entry, onAddProperty]) - const { isPinned, pinProperty, unpinProperty } = usePinnedProperties({ - entry, entries, frontmatter, - onUpdateTypeFrontmatter: onUpdateFrontmatter, - }) - return (