feat: add Rust backend for pinned properties + refactor hook complexity
- Add PinnedPropertyConfig struct to VaultEntry (Rust) - Extract _pinned_properties from YAML frontmatter (key:icon format) - Filter underscore-prefixed system properties from properties/relationships - Bump vault cache version to 9 - Refactor usePinnedProperties hook (cc 15→8) for CodeScene compliance - Add 6 Rust tests for pinned properties extraction - Add pinnedProperties field to TypeScript VaultEntry interface - Wire _pinned_properties mapping in frontmatterOps entry patch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ use super::{parse_md_file, scan_vault, VaultEntry};
|
|||||||
// --- Vault Cache ---
|
// --- Vault Cache ---
|
||||||
|
|
||||||
/// Bump this when VaultEntry fields change to force a full rescan.
|
/// Bump this when VaultEntry fields change to force a full rescan.
|
||||||
const CACHE_VERSION: u32 = 8;
|
const CACHE_VERSION: u32 = 9;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct VaultCache {
|
struct VaultCache {
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct VaultEntry {
|
pub struct VaultEntry {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@@ -59,4 +66,7 @@ pub struct VaultEntry {
|
|||||||
/// Only includes strings, numbers, and booleans — arrays/objects are excluded.
|
/// Only includes strings, numbers, and booleans — arrays/objects are excluded.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub properties: HashMap<String, serde_json::Value>,
|
pub properties: HashMap<String, serde_json::Value>,
|
||||||
|
/// Pinned properties configuration for Type entries.
|
||||||
|
#[serde(rename = "pinnedProperties", default)]
|
||||||
|
pub pinned_properties: Vec<PinnedPropertyConfig>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::vault::entry::PinnedPropertyConfig;
|
||||||
use crate::vault::parsing::contains_wikilink;
|
use crate::vault::parsing::contains_wikilink;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -193,6 +194,9 @@ pub(crate) fn extract_relationships(
|
|||||||
let mut relationships = HashMap::new();
|
let mut relationships = HashMap::new();
|
||||||
|
|
||||||
for (key, value) in data {
|
for (key, value) in data {
|
||||||
|
if key.starts_with('_') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if SKIP_KEYS.iter().any(|k| k.eq_ignore_ascii_case(key)) {
|
if SKIP_KEYS.iter().any(|k| k.eq_ignore_ascii_case(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -228,6 +232,9 @@ pub(crate) fn extract_properties(
|
|||||||
let mut properties = HashMap::new();
|
let mut properties = HashMap::new();
|
||||||
|
|
||||||
for (key, value) in data {
|
for (key, value) in data {
|
||||||
|
if key.starts_with('_') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let lower = key.to_ascii_lowercase();
|
let lower = key.to_ascii_lowercase();
|
||||||
if SKIP_KEYS.iter().any(|k| k.eq_ignore_ascii_case(&lower)) {
|
if SKIP_KEYS.iter().any(|k| k.eq_ignore_ascii_case(&lower)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -265,6 +272,38 @@ pub(crate) fn resolve_is_a(fm_is_a: Option<StringOrList>) -> Option<String> {
|
|||||||
fm_is_a.and_then(|a| a.into_vec().into_iter().next())
|
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<String, serde_json::Value>,
|
||||||
|
) -> Vec<PinnedPropertyConfig> {
|
||||||
|
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
|
/// Convert gray_matter::Pod to serde_json::Value
|
||||||
fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value {
|
fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value {
|
||||||
match pod {
|
match pod {
|
||||||
@@ -284,17 +323,25 @@ fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract frontmatter, relationships, and custom properties from parsed gray_matter data.
|
/// Extract frontmatter, relationships, custom properties, and pinned-property config.
|
||||||
pub(crate) fn extract_fm_and_rels(
|
pub(crate) fn extract_fm_and_rels(
|
||||||
data: Option<gray_matter::Pod>,
|
data: Option<gray_matter::Pod>,
|
||||||
) -> (
|
) -> (
|
||||||
Frontmatter,
|
Frontmatter,
|
||||||
HashMap<String, Vec<String>>,
|
HashMap<String, Vec<String>>,
|
||||||
HashMap<String, serde_json::Value>,
|
HashMap<String, serde_json::Value>,
|
||||||
|
Vec<PinnedPropertyConfig>,
|
||||||
) {
|
) {
|
||||||
let hash = match data {
|
let hash = match data {
|
||||||
Some(gray_matter::Pod::Hash(map)) => map,
|
Some(gray_matter::Pod::Hash(map)) => map,
|
||||||
_ => return (Frontmatter::default(), HashMap::new(), HashMap::new()),
|
_ => {
|
||||||
|
return (
|
||||||
|
Frontmatter::default(),
|
||||||
|
HashMap::new(),
|
||||||
|
HashMap::new(),
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let json_map: HashMap<String, serde_json::Value> =
|
let json_map: HashMap<String, serde_json::Value> =
|
||||||
hash.into_iter().map(|(k, v)| (k, pod_to_json(v))).collect();
|
hash.into_iter().map(|(k, v)| (k, pod_to_json(v))).collect();
|
||||||
@@ -302,5 +349,71 @@ pub(crate) fn extract_fm_and_rels(
|
|||||||
parse_frontmatter(&json_map),
|
parse_frontmatter(&json_map),
|
||||||
extract_relationships(&json_map),
|
extract_relationships(&json_map),
|
||||||
extract_properties(&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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ pub fn parse_md_file(path: &Path) -> Result<VaultEntry, String> {
|
|||||||
|
|
||||||
let matter = Matter::<YAML>::new();
|
let matter = Matter::<YAML>::new();
|
||||||
let parsed = matter.parse(&content);
|
let parsed = matter.parse(&content);
|
||||||
let (frontmatter, mut relationships, properties) = extract_fm_and_rels(parsed.data);
|
let (frontmatter, mut relationships, properties, pinned_properties) =
|
||||||
|
extract_fm_and_rels(parsed.data);
|
||||||
|
|
||||||
let title = extract_title(frontmatter.title.as_deref(), &content, &filename);
|
let title = extract_title(frontmatter.title.as_deref(), &content, &filename);
|
||||||
let snippet = extract_snippet(&content);
|
let snippet = extract_snippet(&content);
|
||||||
@@ -103,6 +104,7 @@ pub fn parse_md_file(path: &Path) -> Result<VaultEntry, String> {
|
|||||||
word_count,
|
word_count,
|
||||||
outgoing_links,
|
outgoing_links,
|
||||||
properties,
|
properties,
|
||||||
|
pinned_properties,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import { RawEditorView } from './RawEditorView'
|
|||||||
import { countWords } from '../utils/wikilinks'
|
import { countWords } from '../utils/wikilinks'
|
||||||
import { SingleEditorView } from './SingleEditorView'
|
import { SingleEditorView } from './SingleEditorView'
|
||||||
import { isEmoji } from '../utils/emoji'
|
import { isEmoji } from '../utils/emoji'
|
||||||
import { PinnedPropertiesBar } from './PinnedPropertiesBar'
|
|
||||||
import { parseFrontmatter } from '../utils/frontmatter'
|
import { parseFrontmatter } from '../utils/frontmatter'
|
||||||
|
import { PinnedPropertiesBar } from './PinnedPropertiesBar'
|
||||||
|
|
||||||
interface Tab {
|
interface Tab {
|
||||||
entry: VaultEntry
|
entry: VaultEntry
|
||||||
@@ -63,6 +63,7 @@ interface EditorContentProps {
|
|||||||
onKeepMine?: (path: string) => void
|
onKeepMine?: (path: string) => void
|
||||||
/** Resolve conflict by keeping the remote version. */
|
/** Resolve conflict by keeping the remote version. */
|
||||||
onKeepTheirs?: (path: string) => void
|
onKeepTheirs?: (path: string) => void
|
||||||
|
/** Update a frontmatter property (for inline pinned-property editing). */
|
||||||
onUpdateFrontmatter?: (path: string, key: string, value: FrontmatterValue) => Promise<void>
|
onUpdateFrontmatter?: (path: string, key: string, value: FrontmatterValue) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function frontmatterToEntryPatch(
|
|||||||
const relPatch: RelationshipPatch = { [key]: null }
|
const relPatch: RelationshipPatch = { [key]: null }
|
||||||
return { patch: ENTRY_DELETE_MAP[k] ?? {}, relationshipPatch: relPatch }
|
return { patch: ENTRY_DELETE_MAP[k] ?? {}, relationshipPatch: relPatch }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle _pinned_properties for Type entries
|
// Handle _pinned_properties for Type entries
|
||||||
if (k === '_pinned_properties' && Array.isArray(value)) {
|
if (k === '_pinned_properties' && Array.isArray(value)) {
|
||||||
const pinned = parsePinnedConfig(value.map(String))
|
const pinned = parsePinnedConfig(value.map(String))
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function buildNewEntry({ path, slug, title, type, status }: NewEntryParam
|
|||||||
aliases: [], belongsTo: [], relatedTo: [],
|
aliases: [], belongsTo: [], relatedTo: [],
|
||||||
status, archived: false, trashed: false, trashedAt: null,
|
status, archived: false, trashed: false, trashedAt: null,
|
||||||
modifiedAt: now, createdAt: now, fileSize: 0,
|
modifiedAt: now, createdAt: now, fileSize: 0,
|
||||||
snippet: '', wordCount: 0, relationships: {}, icon: null, color: null, order: null, outgoingLinks: [], sidebarLabel: null, template: null, sort: null, view: null, visible: null, properties: {},
|
snippet: '', wordCount: 0, relationships: {}, icon: null, color: null, order: null, outgoingLinks: [], sidebarLabel: null, template: null, sort: null, view: null, visible: null, properties: {}, pinnedProperties: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,29 @@ function formatLabel(key: string): string {
|
|||||||
return key.replace(/_/g, ' ')
|
return key.replace(/_/g, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resolve a single pinned config to its display representation. */
|
||||||
|
function resolveOne(
|
||||||
|
entry: VaultEntry, frontmatter: ParsedFrontmatter, cfg: PinnedPropertyConfig,
|
||||||
|
): ResolvedPinnedProperty {
|
||||||
|
const { value, isRelationship } = resolveValue(entry, frontmatter, cfg.key)
|
||||||
|
return { key: cfg.key, icon: resolvePinIcon(cfg.key, cfg.icon), label: formatLabel(cfg.key), value, isRelationship }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find the Type entry for a note's isA field. */
|
||||||
|
function findTypeEntry(entries: VaultEntry[], isA: string | null): VaultEntry | null {
|
||||||
|
if (!isA) return null
|
||||||
|
return entries.find((e) => e.isA === 'Type' && e.title === isA) ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Determine which properties are pinned for a given type. */
|
||||||
|
function resolvePinnedConfigs(
|
||||||
|
isA: string | null, typeEntry: VaultEntry | null, entries: VaultEntry[],
|
||||||
|
): PinnedPropertyConfig[] {
|
||||||
|
if (!isA) return []
|
||||||
|
if (typeEntry && typeEntry.pinnedProperties.length > 0) return typeEntry.pinnedProperties
|
||||||
|
return computeDefaults(entries, isA)
|
||||||
|
}
|
||||||
|
|
||||||
export interface UsePinnedPropertiesResult {
|
export interface UsePinnedPropertiesResult {
|
||||||
pinnedConfigs: PinnedPropertyConfig[]
|
pinnedConfigs: PinnedPropertyConfig[]
|
||||||
resolved: ResolvedPinnedProperty[]
|
resolved: ResolvedPinnedProperty[]
|
||||||
@@ -78,53 +101,31 @@ export interface UsePinnedPropertiesResult {
|
|||||||
isPinned: (key: string) => boolean
|
isPinned: (key: string) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Hook to read and mutate pinned properties for a note based on its type. */
|
||||||
export function usePinnedProperties({
|
export function usePinnedProperties({
|
||||||
entry,
|
entry, entries, frontmatter, onUpdateTypeFrontmatter,
|
||||||
entries,
|
|
||||||
frontmatter,
|
|
||||||
onUpdateTypeFrontmatter,
|
|
||||||
}: {
|
}: {
|
||||||
entry: VaultEntry | null
|
entry: VaultEntry | null
|
||||||
entries: VaultEntry[]
|
entries: VaultEntry[]
|
||||||
frontmatter: ParsedFrontmatter
|
frontmatter: ParsedFrontmatter
|
||||||
onUpdateTypeFrontmatter?: (typePath: string, key: string, value: FrontmatterValue) => Promise<void>
|
onUpdateTypeFrontmatter?: (typePath: string, key: string, value: FrontmatterValue) => Promise<void>
|
||||||
}): UsePinnedPropertiesResult {
|
}): UsePinnedPropertiesResult {
|
||||||
// eslint-disable-next-line react-hooks/preserve-manual-memoization -- type lookup intentionally memoized
|
const typeEntry = useMemo(() => findTypeEntry(entries, entry?.isA ?? null), [entry?.isA, entries])
|
||||||
const typeEntry = useMemo(() => {
|
const pinnedConfigs = useMemo(
|
||||||
if (!entry?.isA) return null
|
() => resolvePinnedConfigs(entry?.isA ?? null, typeEntry, entries),
|
||||||
return entries.find((e) => e.isA === 'Type' && e.title === entry.isA) ?? null
|
[entry?.isA, typeEntry, entries],
|
||||||
}, [entry?.isA, entries])
|
)
|
||||||
|
const resolved = useMemo(
|
||||||
// eslint-disable-next-line react-hooks/preserve-manual-memoization -- pin config intentionally memoized
|
() => (entry ? pinnedConfigs.map((cfg) => resolveOne(entry, frontmatter, cfg)) : []),
|
||||||
const pinnedConfigs = useMemo((): PinnedPropertyConfig[] => {
|
[entry, pinnedConfigs, frontmatter],
|
||||||
if (!entry?.isA) return []
|
)
|
||||||
if (typeEntry && typeEntry.pinnedProperties.length > 0) return typeEntry.pinnedProperties
|
|
||||||
return computeDefaults(entries, entry.isA)
|
|
||||||
}, [entry?.isA, typeEntry, entries])
|
|
||||||
|
|
||||||
const resolved = useMemo((): ResolvedPinnedProperty[] => {
|
|
||||||
if (!entry) return []
|
|
||||||
return pinnedConfigs.map((cfg) => {
|
|
||||||
const { value, isRelationship } = resolveValue(entry, frontmatter, cfg.key)
|
|
||||||
return {
|
|
||||||
key: cfg.key,
|
|
||||||
icon: resolvePinIcon(cfg.key, cfg.icon),
|
|
||||||
label: formatLabel(cfg.key),
|
|
||||||
value,
|
|
||||||
isRelationship,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [entry, pinnedConfigs, frontmatter])
|
|
||||||
|
|
||||||
const savePins = useCallback(
|
const savePins = useCallback(
|
||||||
(newConfigs: PinnedPropertyConfig[]) => {
|
(newConfigs: PinnedPropertyConfig[]) => {
|
||||||
if (!typeEntry || !onUpdateTypeFrontmatter) return
|
if (!typeEntry || !onUpdateTypeFrontmatter) return
|
||||||
const serialised = serialisePinnedConfig(newConfigs)
|
onUpdateTypeFrontmatter(typeEntry.path, '_pinned_properties', serialisePinnedConfig(newConfigs))
|
||||||
onUpdateTypeFrontmatter(typeEntry.path, '_pinned_properties', serialised)
|
|
||||||
},
|
},
|
||||||
[typeEntry, onUpdateTypeFrontmatter],
|
[typeEntry, onUpdateTypeFrontmatter],
|
||||||
)
|
)
|
||||||
|
|
||||||
const pinProperty = useCallback(
|
const pinProperty = useCallback(
|
||||||
(key: string, icon?: string) => {
|
(key: string, icon?: string) => {
|
||||||
if (pinnedConfigs.some((c) => c.key === key)) return
|
if (pinnedConfigs.some((c) => c.key === key)) return
|
||||||
@@ -132,26 +133,16 @@ export function usePinnedProperties({
|
|||||||
},
|
},
|
||||||
[pinnedConfigs, savePins],
|
[pinnedConfigs, savePins],
|
||||||
)
|
)
|
||||||
|
|
||||||
const unpinProperty = useCallback(
|
const unpinProperty = useCallback(
|
||||||
(key: string) => savePins(pinnedConfigs.filter((c) => c.key !== key)),
|
(key: string) => savePins(pinnedConfigs.filter((c) => c.key !== key)),
|
||||||
[pinnedConfigs, savePins],
|
[pinnedConfigs, savePins],
|
||||||
)
|
)
|
||||||
|
|
||||||
const updatePinIcon = useCallback(
|
const updatePinIcon = useCallback(
|
||||||
(key: string, icon: string) => savePins(pinnedConfigs.map((c) => (c.key === key ? { ...c, icon } : c))),
|
(key: string, icon: string) => savePins(pinnedConfigs.map((c) => (c.key === key ? { ...c, icon } : c))),
|
||||||
[pinnedConfigs, savePins],
|
[pinnedConfigs, savePins],
|
||||||
)
|
)
|
||||||
|
const reorderPins = useCallback((configs: PinnedPropertyConfig[]) => savePins(configs), [savePins])
|
||||||
const reorderPins = useCallback(
|
const isPinned = useCallback((key: string) => pinnedConfigs.some((c) => c.key === key), [pinnedConfigs])
|
||||||
(configs: PinnedPropertyConfig[]) => savePins(configs),
|
|
||||||
[savePins],
|
|
||||||
)
|
|
||||||
|
|
||||||
const isPinned = useCallback(
|
|
||||||
(key: string) => pinnedConfigs.some((c) => c.key === key),
|
|
||||||
[pinnedConfigs],
|
|
||||||
)
|
|
||||||
|
|
||||||
return { pinnedConfigs, resolved, pinProperty, unpinProperty, updatePinIcon, reorderPins, isPinned }
|
return { pinnedConfigs, resolved, pinProperty, unpinProperty, updatePinIcon, reorderPins, isPinned }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ function collectAllVaultTags(entries: VaultEntry[] | undefined): Record<string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isVisibleProperty([key, value]: [string, FrontmatterValue]): boolean {
|
function isVisibleProperty([key, value]: [string, FrontmatterValue]): boolean {
|
||||||
|
if (key.startsWith('_')) return false
|
||||||
return !SKIP_KEYS.has(key.toLowerCase()) && !containsWikilinks(value)
|
return !SKIP_KEYS.has(key.toLowerCase()) && !containsWikilinks(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['q1-2026', 'software-development', 'matteo-cellini', 'maria-bianchi', 'marco-verdi'],
|
outgoingLinks: ['q1-2026', 'software-development', 'matteo-cellini', 'maria-bianchi', 'marco-verdi'],
|
||||||
properties: { Priority: 'High', 'Due date': '2026-06-15', Owner: 'Luca Rossi' },
|
properties: { Priority: 'High', 'Due date': '2026-06-15', Owner: 'Luca Rossi' },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/grow-newsletter.md',
|
path: '/Users/luca/Laputa/grow-newsletter.md',
|
||||||
@@ -72,6 +73,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['on-writing-well', 'engineering-leadership-101', 'ai-agents-primer', 'growth', 'writing'],
|
outgoingLinks: ['on-writing-well', 'engineering-leadership-101', 'ai-agents-primer', 'growth', 'writing'],
|
||||||
properties: { Priority: 'High', Rating: 5, Cadence: 'Weekly', Owner: 'Luca Rossi' },
|
properties: { Priority: 'High', Rating: 5, Cadence: 'Weekly', Owner: 'Luca Rossi' },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/manage-sponsorships.md',
|
path: '/Users/luca/Laputa/manage-sponsorships.md',
|
||||||
@@ -101,6 +103,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['matteo-cellini'],
|
outgoingLinks: ['matteo-cellini'],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/write-weekly-essays.md',
|
path: '/Users/luca/Laputa/write-weekly-essays.md',
|
||||||
@@ -130,6 +133,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['grow-newsletter'],
|
outgoingLinks: ['grow-newsletter'],
|
||||||
properties: { Owner: 'Luca Rossi', Cadence: 'Weekly' },
|
properties: { Owner: 'Luca Rossi', Cadence: 'Weekly' },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/run-sponsorships.md',
|
path: '/Users/luca/Laputa/run-sponsorships.md',
|
||||||
@@ -159,6 +163,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['manage-sponsorships'],
|
outgoingLinks: ['manage-sponsorships'],
|
||||||
properties: { Owner: 'Matteo Cellini', Cadence: 'Weekly' },
|
properties: { Owner: 'Matteo Cellini', Cadence: 'Weekly' },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/stock-screener.md',
|
path: '/Users/luca/Laputa/stock-screener.md',
|
||||||
@@ -189,6 +194,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['trading', 'algorithmic-trading', 'ema200-backtest-results'],
|
outgoingLinks: ['trading', 'algorithmic-trading', 'ema200-backtest-results'],
|
||||||
properties: { Priority: 'Low', 'Due date': '2026-03-01', Owner: 'Luca Rossi' },
|
properties: { Priority: 'Low', 'Due date': '2026-03-01', Owner: 'Luca Rossi' },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/facebook-ads-strategy.md',
|
path: '/Users/luca/Laputa/facebook-ads-strategy.md',
|
||||||
@@ -219,6 +225,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['26q1-laputa-app', 'growth', 'ads'],
|
outgoingLinks: ['26q1-laputa-app', 'growth', 'ads'],
|
||||||
properties: { Priority: 'Medium', Rating: 4 },
|
properties: { Priority: 'Medium', Rating: 4 },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/budget-allocation.md',
|
path: '/Users/luca/Laputa/budget-allocation.md',
|
||||||
@@ -248,6 +255,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['26q1-laputa-app'],
|
outgoingLinks: ['26q1-laputa-app'],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/matteo-cellini.md',
|
path: '/Users/luca/Laputa/matteo-cellini.md',
|
||||||
@@ -276,6 +284,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: { Company: 'Acme Corp', Role: 'Engineering Lead' },
|
properties: { Company: 'Acme Corp', Role: 'Engineering Lead' },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/maria-bianchi.md',
|
path: '/Users/luca/Laputa/maria-bianchi.md',
|
||||||
@@ -304,6 +313,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: { Company: 'TechStart', Role: 'Product Manager' },
|
properties: { Company: 'TechStart', Role: 'Product Manager' },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/marco-verdi.md',
|
path: '/Users/luca/Laputa/marco-verdi.md',
|
||||||
@@ -332,6 +342,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/elena-russo.md',
|
path: '/Users/luca/Laputa/elena-russo.md',
|
||||||
@@ -360,6 +371,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/2026-02-14-laputa-app-kickoff.md',
|
path: '/Users/luca/Laputa/2026-02-14-laputa-app-kickoff.md',
|
||||||
@@ -389,6 +401,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['26q1-laputa-app', 'matteo-cellini'],
|
outgoingLinks: ['26q1-laputa-app', 'matteo-cellini'],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/software-development.md',
|
path: '/Users/luca/Laputa/software-development.md',
|
||||||
@@ -418,6 +431,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/trading.md',
|
path: '/Users/luca/Laputa/trading.md',
|
||||||
@@ -447,6 +461,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/on-writing-well.md',
|
path: '/Users/luca/Laputa/on-writing-well.md',
|
||||||
@@ -476,6 +491,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['grow-newsletter'],
|
outgoingLinks: ['grow-newsletter'],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/engineering-leadership-101.md',
|
path: '/Users/luca/Laputa/engineering-leadership-101.md',
|
||||||
@@ -506,6 +522,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['grow-newsletter', 'software-development'],
|
outgoingLinks: ['grow-newsletter', 'software-development'],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/ai-agents-primer.md',
|
path: '/Users/luca/Laputa/ai-agents-primer.md',
|
||||||
@@ -535,6 +552,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: ['grow-newsletter'],
|
outgoingLinks: ['grow-newsletter'],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
// --- Type documents ---
|
// --- Type documents ---
|
||||||
{
|
{
|
||||||
@@ -562,6 +580,11 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [
|
||||||
|
{ key: 'Status', icon: 'circle-dot' },
|
||||||
|
{ key: 'Belongs to', icon: 'arrow-up-right' },
|
||||||
|
{ key: 'Due date', icon: 'calendar' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/responsibility.md',
|
path: '/Users/luca/Laputa/responsibility.md',
|
||||||
@@ -588,6 +611,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/procedure.md',
|
path: '/Users/luca/Laputa/procedure.md',
|
||||||
@@ -614,6 +638,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/experiment.md',
|
path: '/Users/luca/Laputa/experiment.md',
|
||||||
@@ -640,6 +665,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: false,
|
template: null, sort: null, view: null, visible: false,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/person.md',
|
path: '/Users/luca/Laputa/person.md',
|
||||||
@@ -666,6 +692,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/event.md',
|
path: '/Users/luca/Laputa/event.md',
|
||||||
@@ -692,6 +719,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/topic.md',
|
path: '/Users/luca/Laputa/topic.md',
|
||||||
@@ -718,6 +746,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/essay.md',
|
path: '/Users/luca/Laputa/essay.md',
|
||||||
@@ -744,6 +773,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/note.md',
|
path: '/Users/luca/Laputa/note.md',
|
||||||
@@ -770,6 +800,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
// --- Custom type documents ---
|
// --- Custom type documents ---
|
||||||
{
|
{
|
||||||
@@ -797,6 +828,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/recipe.md',
|
path: '/Users/luca/Laputa/recipe.md',
|
||||||
@@ -823,6 +855,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/book.md',
|
path: '/Users/luca/Laputa/book.md',
|
||||||
@@ -849,6 +882,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
// --- Instances of custom types ---
|
// --- Instances of custom types ---
|
||||||
{
|
{
|
||||||
@@ -878,6 +912,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/pasta-carbonara.md',
|
path: '/Users/luca/Laputa/pasta-carbonara.md',
|
||||||
@@ -906,6 +941,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: { Difficulty: 'Easy', 'Prep time': '30 min', Servings: 4 },
|
properties: { Difficulty: 'Easy', 'Prep time': '30 min', Servings: 4 },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/designing-data-intensive-applications.md',
|
path: '/Users/luca/Laputa/designing-data-intensive-applications.md',
|
||||||
@@ -934,6 +970,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: { Author: 'Martin Kleppmann', Rating: 5, 'Year published': 2017 },
|
properties: { Author: 'Martin Kleppmann', Rating: 5, 'Year published': 2017 },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
// --- Trashed entries ---
|
// --- Trashed entries ---
|
||||||
{
|
{
|
||||||
@@ -964,6 +1001,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/deprecated-api-notes.md',
|
path: '/Users/luca/Laputa/deprecated-api-notes.md',
|
||||||
@@ -992,6 +1030,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/failed-seo-experiment.md',
|
path: '/Users/luca/Laputa/failed-seo-experiment.md',
|
||||||
@@ -1021,6 +1060,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: { Owner: 'Luca Rossi' },
|
properties: { Owner: 'Luca Rossi' },
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
// --- Archived entries ---
|
// --- Archived entries ---
|
||||||
{
|
{
|
||||||
@@ -1042,6 +1082,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: { Owner: 'Luca Rossi' },
|
properties: { Owner: 'Luca Rossi' },
|
||||||
|
pinnedProperties: [],
|
||||||
modifiedAt: now - 86400 * 120,
|
modifiedAt: now - 86400 * 120,
|
||||||
createdAt: now - 86400 * 200,
|
createdAt: now - 86400 * 200,
|
||||||
fileSize: 680,
|
fileSize: 680,
|
||||||
@@ -1071,6 +1112,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: { Owner: 'Luca Rossi' },
|
properties: { Owner: 'Luca Rossi' },
|
||||||
|
pinnedProperties: [],
|
||||||
modifiedAt: now - 86400 * 90,
|
modifiedAt: now - 86400 * 90,
|
||||||
createdAt: now - 86400 * 150,
|
createdAt: now - 86400 * 150,
|
||||||
fileSize: 520,
|
fileSize: 520,
|
||||||
@@ -1107,6 +1149,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/refactoring-ideas.md',
|
path: '/Users/luca/Laputa/refactoring-ideas.md',
|
||||||
@@ -1133,6 +1176,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/refactoring-key-ideas.md',
|
path: '/Users/luca/Laputa/refactoring-key-ideas.md',
|
||||||
@@ -1159,6 +1203,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/Users/luca/Laputa/refactoring-patterns.md',
|
path: '/Users/luca/Laputa/refactoring-patterns.md',
|
||||||
@@ -1185,6 +1230,7 @@ export const MOCK_ENTRIES: VaultEntry[] = [
|
|||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
outgoingLinks: [],
|
outgoingLinks: [],
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1237,6 +1283,7 @@ function generateBulkEntries(count: number): VaultEntry[] {
|
|||||||
sidebarLabel: null,
|
sidebarLabel: null,
|
||||||
template: null, sort: null, view: null, visible: null,
|
template: null, sort: null, view: null, visible: null,
|
||||||
properties: {},
|
properties: {},
|
||||||
|
pinnedProperties: [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ const trimOrNull = (v: string | null | undefined): string | null => v?.trim() ||
|
|||||||
export const mockHandlers: Record<string, (args: any) => any> = {
|
export const mockHandlers: Record<string, (args: any) => any> = {
|
||||||
list_vault: () => MOCK_ENTRIES,
|
list_vault: () => MOCK_ENTRIES,
|
||||||
reload_vault: () => MOCK_ENTRIES,
|
reload_vault: () => MOCK_ENTRIES,
|
||||||
reload_vault_entry: (args: { path: string }) => MOCK_ENTRIES.find(e => e.path === args.path) ?? { path: args.path, title: 'Unknown', filename: 'unknown.md', aliases: [], belongsTo: [], relatedTo: [], archived: false, trashed: false, snippet: '', wordCount: 0, fileSize: 0, relationships: {}, outgoingLinks: [], properties: {} },
|
reload_vault_entry: (args: { path: string }) => MOCK_ENTRIES.find(e => e.path === args.path) ?? { path: args.path, title: 'Unknown', filename: 'unknown.md', aliases: [], belongsTo: [], relatedTo: [], archived: false, trashed: false, snippet: '', wordCount: 0, fileSize: 0, relationships: {}, outgoingLinks: [], properties: {}, pinnedProperties: [] },
|
||||||
sync_note_title: () => false,
|
sync_note_title: () => false,
|
||||||
get_note_content: (args: { path: string }) => MOCK_CONTENT[args.path] ?? '',
|
get_note_content: (args: { path: string }) => MOCK_CONTENT[args.path] ?? '',
|
||||||
get_all_content: () => MOCK_CONTENT,
|
get_all_content: () => MOCK_CONTENT,
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/** A single pinned-property config item from a Type's `_pinned_properties` list. */
|
||||||
|
export interface PinnedPropertyConfig {
|
||||||
|
key: string
|
||||||
|
icon: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export interface VaultEntry {
|
export interface VaultEntry {
|
||||||
path: string
|
path: string
|
||||||
filename: string
|
filename: string
|
||||||
@@ -39,6 +45,8 @@ export interface VaultEntry {
|
|||||||
outgoingLinks: string[]
|
outgoingLinks: string[]
|
||||||
/** Custom scalar frontmatter properties (non-relationship, non-structural). */
|
/** Custom scalar frontmatter properties (non-relationship, non-structural). */
|
||||||
properties: Record<string, string | number | boolean | null>
|
properties: Record<string, string | number | boolean | null>
|
||||||
|
/** Pinned properties config for Type entries. Parsed from `_pinned_properties` frontmatter. */
|
||||||
|
pinnedProperties: PinnedPropertyConfig[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NoteStatus = 'new' | 'modified' | 'clean' | 'pendingSave' | 'unsaved'
|
export type NoteStatus = 'new' | 'modified' | 'clean' | 'pendingSave' | 'unsaved'
|
||||||
|
|||||||
Reference in New Issue
Block a user