fix: tolerate colliding frontmatter properties
This commit is contained in:
@@ -11,7 +11,7 @@ files:
|
||||
command.openSettings: 638c05f4f159a403e04aebe94b46a2a8
|
||||
command.openSettings.keywords: ab6b5e03395f4db955ed9cbfd4de33b7
|
||||
command.openLanguageSettings: e5a425aa6222fab3df66c0e1e0ca4183
|
||||
command.openLanguageSettings.keywords: 639afefdd1c238b381bc1f963288f389
|
||||
command.openLanguageSettings.keywords: 6a910000cbf23eac3c7078e518cae66a
|
||||
command.useSystemLanguage: b7f0315c47d4a59faa2a49571fcf1fca
|
||||
command.openH1Setting: 05b4f6edc0e98b29670e8ee902cdb9bf
|
||||
command.toggleGitignoredFilesVisibility: 0a2d3ea4e8ff8b00a801183caeb97a03
|
||||
@@ -385,6 +385,8 @@ files:
|
||||
inspector.title.properties: 9fc2d28c05ed9eb1d75ba4465abf15a9
|
||||
inspector.title.propertiesShortcut: 427d1cbdc813cf54c5fac2508d38d407
|
||||
inspector.title.closePropertiesShortcut: 97b953e45042d4143dd19624dc345d29
|
||||
inspector.title.collidingProperties: bf71358d0af34696e88bea278eac046a
|
||||
inspector.title.collidingPropertiesAria: ce5c3569f304cd69b0f33a6fc185f49b
|
||||
inspector.empty.noNoteSelected: 046d95682b747a30ed8a7b0b1d581629
|
||||
inspector.empty.noProperties: 6864166e0f65d81b1eb474e41fde8822
|
||||
inspector.empty.initializeProperties: edf249d214b68f5afe8634c577b709c4
|
||||
@@ -525,3 +527,4 @@ files:
|
||||
locale.jaJP: f32ced6a9ba164c4b3c047fd1d7c882e
|
||||
locale.koKR: d0bdb3cde477d82e766da05ebda50ccb
|
||||
locale.vi: 7b80fae85640c16cdb0261bef0c27636
|
||||
locale.plPL: c730389bc8d99e59c867766babdd48b5
|
||||
|
||||
@@ -182,69 +182,78 @@ fn sanitize_value(value: &serde_json::Value) -> serde_json::Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn type_key_priority(key: &str) -> u8 {
|
||||
match key {
|
||||
"type" => 0,
|
||||
"Type" => 1,
|
||||
"TYPE" => 2,
|
||||
_ => 3,
|
||||
fn canonical_known_key(key: &str) -> Option<&'static str> {
|
||||
let trimmed = key.trim();
|
||||
if trimmed.eq_ignore_ascii_case("type") {
|
||||
return Some("type");
|
||||
}
|
||||
match trimmed {
|
||||
"title" => Some("title"),
|
||||
"Is A" | "is_a" => Some("type"),
|
||||
"aliases" => Some("aliases"),
|
||||
"_archived" | "Archived" | "archived" => Some("_archived"),
|
||||
"_icon" | "icon" => Some("_icon"),
|
||||
"color" => Some("color"),
|
||||
"_order" | "order" => Some("_order"),
|
||||
"_sidebar_label" | "sidebar_label" | "sidebar label" => Some("_sidebar_label"),
|
||||
"template" => Some("template"),
|
||||
"_sort" | "sort" => Some("_sort"),
|
||||
"view" => Some("view"),
|
||||
"_width" | "width" => Some("_width"),
|
||||
"visible" => Some("visible"),
|
||||
"Status" | "status" => Some("Status"),
|
||||
"_organized" => Some("_organized"),
|
||||
"_favorite" => Some("_favorite"),
|
||||
"_favorite_index" => Some("_favorite_index"),
|
||||
"_list_properties_display" => Some("_list_properties_display"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_type_value(data: &HashMap<String, serde_json::Value>) -> Option<&serde_json::Value> {
|
||||
data.iter()
|
||||
.filter(|(key, _)| key.eq_ignore_ascii_case("type"))
|
||||
.min_by_key(|(key, _)| type_key_priority(key))
|
||||
.map(|(_, value)| value)
|
||||
fn insert_known_frontmatter_value(
|
||||
target: &mut serde_json::Map<String, serde_json::Value>,
|
||||
key: &str,
|
||||
value: &serde_json::Value,
|
||||
overwrite: bool,
|
||||
) {
|
||||
let Some(canonical_key) = canonical_known_key(key) else {
|
||||
return;
|
||||
};
|
||||
if overwrite || !target.contains_key(canonical_key) {
|
||||
target.insert(canonical_key.to_string(), sanitize_value(value));
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_frontmatter_keys(raw_content: &str) -> Vec<String> {
|
||||
RawFrontmatter(raw_content)
|
||||
.extract_block()
|
||||
.map(|raw| {
|
||||
raw.lines()
|
||||
.filter_map(|line| YamlLine(line).key().map(str::to_string))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn known_frontmatter_map(
|
||||
data: &HashMap<String, serde_json::Value>,
|
||||
raw_content: &str,
|
||||
) -> serde_json::Map<String, serde_json::Value> {
|
||||
let mut filtered = serde_json::Map::new();
|
||||
for key in raw_frontmatter_keys(raw_content) {
|
||||
if let Some(value) = data.get(&key) {
|
||||
insert_known_frontmatter_value(&mut filtered, &key, value, true);
|
||||
}
|
||||
}
|
||||
for (key, value) in data {
|
||||
insert_known_frontmatter_value(&mut filtered, key, value, false);
|
||||
}
|
||||
filtered
|
||||
}
|
||||
|
||||
/// Parse frontmatter from raw YAML data extracted by gray_matter.
|
||||
fn parse_frontmatter(data: &HashMap<String, serde_json::Value>) -> Frontmatter {
|
||||
static KNOWN_KEYS: &[&str] = &[
|
||||
"title",
|
||||
"type",
|
||||
"Is A",
|
||||
"is_a",
|
||||
"aliases",
|
||||
"_archived",
|
||||
"Archived",
|
||||
"archived",
|
||||
"_icon",
|
||||
"icon",
|
||||
"color",
|
||||
"_order",
|
||||
"order",
|
||||
"_sidebar_label",
|
||||
"sidebar_label",
|
||||
"sidebar label",
|
||||
"template",
|
||||
"_sort",
|
||||
"sort",
|
||||
"view",
|
||||
"_width",
|
||||
"width",
|
||||
"visible",
|
||||
"notion_id",
|
||||
"Status",
|
||||
"status",
|
||||
"_organized",
|
||||
"_favorite",
|
||||
"_favorite_index",
|
||||
"_list_properties_display",
|
||||
];
|
||||
let mut filtered = serde_json::Map::new();
|
||||
|
||||
if let Some(value) = find_type_value(data) {
|
||||
filtered.insert("type".to_string(), sanitize_value(value));
|
||||
}
|
||||
|
||||
for (key, value) in data {
|
||||
if key.eq_ignore_ascii_case("type") || !KNOWN_KEYS.contains(&key.as_str()) {
|
||||
continue;
|
||||
}
|
||||
filtered.insert(key.clone(), sanitize_value(value));
|
||||
}
|
||||
|
||||
fn parse_frontmatter(data: &HashMap<String, serde_json::Value>, raw_content: &str) -> Frontmatter {
|
||||
let filtered = known_frontmatter_map(data, raw_content);
|
||||
let value = serde_json::Value::Object(filtered);
|
||||
serde_json::from_value(value).unwrap_or_default()
|
||||
}
|
||||
@@ -443,7 +452,7 @@ impl<'a> YamlLine<'a> {
|
||||
return None;
|
||||
}
|
||||
let (key, _) = self.0.split_once(':')?;
|
||||
Some(key.trim().trim_matches('"'))
|
||||
Some(key.trim().trim_matches('"').trim_matches('\''))
|
||||
}
|
||||
|
||||
fn list_item(self) -> Option<&'a str> {
|
||||
@@ -543,7 +552,7 @@ pub(crate) fn extract_fm_and_rels(
|
||||
}
|
||||
};
|
||||
(
|
||||
parse_frontmatter(&json_map),
|
||||
parse_frontmatter(&json_map, raw_content),
|
||||
extract_relationships(&json_map),
|
||||
extract_properties(&json_map),
|
||||
)
|
||||
|
||||
@@ -133,3 +133,13 @@ fn test_alias_parser_recovers_special_alias_items() {
|
||||
assert_alias_parser_recovers(case);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alias_collisions_keep_frontmatter_with_last_value_winning() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let content = "---\ntype: Note\nstatus: Active\nStatus: Evergreened\n---\n# Test\n";
|
||||
let entry = parse_test_entry(&dir, "test.md", content);
|
||||
|
||||
assert_eq!(entry.is_a, Some("Note".to_string()));
|
||||
assert_eq!(entry.status, Some("Evergreened".to_string()));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { describe, it, expect, vi } from 'vitest'
|
||||
import { CreateViewDialog } from './CreateViewDialog'
|
||||
import type { ViewDefinition } from '../types'
|
||||
|
||||
const DIALOG_TEST_TIMEOUT_MS = 10_000
|
||||
|
||||
describe('CreateViewDialog', () => {
|
||||
const defaultProps = {
|
||||
open: true,
|
||||
@@ -26,7 +28,7 @@ describe('CreateViewDialog', () => {
|
||||
render(<CreateViewDialog {...defaultProps} />)
|
||||
expect(screen.getByText('Create View')).toBeInTheDocument()
|
||||
expect(screen.getByText('Create')).toBeInTheDocument()
|
||||
})
|
||||
}, DIALOG_TEST_TIMEOUT_MS)
|
||||
|
||||
it('shows "Edit View" title when editingView is provided', () => {
|
||||
render(<CreateViewDialog {...defaultProps} editingView={makeEditingView()} />)
|
||||
|
||||
@@ -129,6 +129,26 @@ describe('Inspector', () => {
|
||||
expect(onToggle).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('shows a colliding-properties warning that opens the raw editor', async () => {
|
||||
const onToggleRawEditor = vi.fn()
|
||||
const content = `---
|
||||
type: Note
|
||||
status: Active
|
||||
Status: Evergreened
|
||||
---
|
||||
# Test Project
|
||||
`
|
||||
|
||||
renderSelectedInspector({ content, onToggleRawEditor })
|
||||
|
||||
const warning = screen.getByRole('button', { name: 'Colliding properties. Open raw editor.' })
|
||||
fireEvent.focus(warning)
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent('Colliding properties')
|
||||
|
||||
fireEvent.click(warning)
|
||||
expect(onToggleRawEditor).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('shows properties when a note is selected', () => {
|
||||
render(<Inspector {...defaultProps} entry={mockEntry} content={mockContent} />)
|
||||
expect(screen.getAllByText('Project').length).toBeGreaterThan(0)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo } from 'react'
|
||||
import type { VaultEntry, GitCommit } from '../types'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Separator } from './ui/separator'
|
||||
import { parseFrontmatter, detectFrontmatterState } from '../utils/frontmatter'
|
||||
import { parseFrontmatter, detectFrontmatterState, detectFrontmatterWarnings } from '../utils/frontmatter'
|
||||
import { DynamicPropertiesPanel } from './DynamicPropertiesPanel'
|
||||
import {
|
||||
DynamicRelationshipsPanel,
|
||||
@@ -247,9 +247,20 @@ function InspectorBody({
|
||||
}
|
||||
|
||||
export function Inspector({ collapsed, onToggle, ...bodyProps }: InspectorProps) {
|
||||
const frontmatterWarnings = useMemo(
|
||||
() => detectFrontmatterWarnings(bodyProps.content),
|
||||
[bodyProps.content],
|
||||
)
|
||||
|
||||
return (
|
||||
<aside className={cn('flex flex-1 flex-col overflow-hidden border-l border-border bg-background text-foreground transition-[width] duration-200', collapsed && '!w-10 !min-w-10')}>
|
||||
<InspectorHeader collapsed={collapsed} locale={bodyProps.locale} onToggle={onToggle} />
|
||||
<InspectorHeader
|
||||
collapsed={collapsed}
|
||||
frontmatterWarnings={frontmatterWarnings}
|
||||
locale={bodyProps.locale}
|
||||
onToggle={onToggle}
|
||||
onOpenRawEditor={bodyProps.onToggleRawEditor}
|
||||
/>
|
||||
{!collapsed && (
|
||||
<div className="flex flex-1 flex-col gap-4 overflow-y-auto p-3">
|
||||
<InspectorBody {...bodyProps} />
|
||||
|
||||
@@ -2,6 +2,8 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { LinuxMenuButton } from './LinuxMenuButton'
|
||||
|
||||
const MENU_TEST_TIMEOUT_MS = 10_000
|
||||
|
||||
const { close, invoke, minimize, toggleMaximize } = vi.hoisted(() => ({
|
||||
invoke: vi.fn().mockResolvedValue(undefined),
|
||||
minimize: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -41,7 +43,7 @@ describe('LinuxMenuButton', () => {
|
||||
expect(screen.getByText('Ctrl+Shift+L')).toBeInTheDocument()
|
||||
fireEvent.click(await screen.findByText('Toggle AI Panel'))
|
||||
expect(invoke).toHaveBeenCalledWith('trigger_menu_command', { id: 'view-toggle-ai-chat' })
|
||||
})
|
||||
}, MENU_TEST_TIMEOUT_MS)
|
||||
|
||||
it('invokes direct window actions from the Window submenu', async () => {
|
||||
render(<LinuxMenuButton />)
|
||||
|
||||
@@ -1,10 +1,40 @@
|
||||
import { SlidersHorizontal, X, Sparkle, WarningCircle, PencilSimple } from '@phosphor-icons/react'
|
||||
import { ActionTooltip } from '@/components/ui/action-tooltip'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useDragRegion } from '../../hooks/useDragRegion'
|
||||
import { translate, type AppLocale } from '../../lib/i18n'
|
||||
import { hasFrontmatterWarnings, type FrontmatterWarnings } from '../../utils/frontmatter'
|
||||
|
||||
export function InspectorHeader({ collapsed, locale = 'en', onToggle }: { collapsed: boolean; locale?: AppLocale; onToggle: () => void }) {
|
||||
function FrontmatterWarningsButton({ locale, onOpenRawEditor }: { locale: AppLocale; onOpenRawEditor: () => void }) {
|
||||
const label = translate(locale, 'inspector.title.collidingProperties')
|
||||
return (
|
||||
<ActionTooltip copy={{ label }} side="bottom">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="h-6 w-6 shrink-0 rounded-md border border-[var(--feedback-warning-border)] bg-[var(--feedback-warning-bg)] p-0 text-[var(--feedback-warning-text)] shadow-none hover:brightness-95"
|
||||
aria-label={translate(locale, 'inspector.title.collidingPropertiesAria')}
|
||||
data-testid="frontmatter-warnings-button"
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onClick={onOpenRawEditor}
|
||||
>
|
||||
<WarningCircle size={14} weight="fill" aria-hidden="true" />
|
||||
</Button>
|
||||
</ActionTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export function InspectorHeader({ collapsed, frontmatterWarnings, locale = 'en', onToggle, onOpenRawEditor }: {
|
||||
collapsed: boolean
|
||||
frontmatterWarnings?: FrontmatterWarnings
|
||||
locale?: AppLocale
|
||||
onToggle: () => void
|
||||
onOpenRawEditor?: () => void
|
||||
}) {
|
||||
const { onMouseDown } = useDragRegion()
|
||||
const propertiesTitle = translate(locale, 'inspector.title.properties')
|
||||
const showWarnings = Boolean(frontmatterWarnings && hasFrontmatterWarnings(frontmatterWarnings) && onOpenRawEditor)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -23,7 +53,11 @@ export function InspectorHeader({ collapsed, locale = 'en', onToggle }: { collap
|
||||
) : (
|
||||
<>
|
||||
<SlidersHorizontal size={16} className="shrink-0 text-muted-foreground" />
|
||||
<span className="flex-1 text-muted-foreground" style={{ fontSize: 13, fontWeight: 600 }}>{propertiesTitle}</span>
|
||||
<span className="text-muted-foreground" style={{ fontSize: 13, fontWeight: 600 }}>{propertiesTitle}</span>
|
||||
{showWarnings && onOpenRawEditor && (
|
||||
<FrontmatterWarningsButton locale={locale} onOpenRawEditor={onOpenRawEditor} />
|
||||
)}
|
||||
<span className="flex-1" />
|
||||
<button
|
||||
className="shrink-0 border-none bg-transparent p-1 text-muted-foreground cursor-pointer hover:text-foreground"
|
||||
onClick={onToggle}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Einstellungen öffnen",
|
||||
"command.openSettings.keywords": "Einstellungen Konfiguration",
|
||||
"command.openLanguageSettings": "Spracheinstellungen öffnen",
|
||||
"command.openLanguageSettings.keywords": "Sprache Gebietsschema i18n Internationalisierung Lokalisierung Englisch Italienisch Französisch Deutsch Russisch Spanisch Portugiesisch Chinesisch Vereinfacht Traditionell Japanisch Koreanisch 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "Sprache Gebietsschema i18n Internationalisierung Lokalisierung Englisch Italienisch Französisch Deutsch Russisch Spanisch Portugiesisch Chinesisch vereinfacht traditionell Japanisch Koreanisch Polnisch 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Systemsprache verwenden",
|
||||
"command.openH1Setting": "Einstellung für automatische H1-Umbenennung öffnen",
|
||||
"command.toggleGitignoredFilesVisibility": "Sichtbarkeit von Gitignored-Dateien umschalten",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Eigenschaften",
|
||||
"inspector.title.propertiesShortcut": "Eigenschaften (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Eigenschaften schließen (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Kollidierende Eigenschaften",
|
||||
"inspector.title.collidingPropertiesAria": "Kollidierende Eigenschaften. Roh-Editor öffnen.",
|
||||
"inspector.empty.noNoteSelected": "Keine Notiz ausgewählt",
|
||||
"inspector.empty.noProperties": "Diese Notiz hat noch keine Eigenschaften",
|
||||
"inspector.empty.initializeProperties": "Eigenschaften initialisieren",
|
||||
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Properties",
|
||||
"inspector.title.propertiesShortcut": "Properties (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Close Properties (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Colliding properties",
|
||||
"inspector.title.collidingPropertiesAria": "Colliding properties. Open raw editor.",
|
||||
"inspector.empty.noNoteSelected": "No note selected",
|
||||
"inspector.empty.noProperties": "This note has no properties yet",
|
||||
"inspector.empty.initializeProperties": "Initialize properties",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Abrir Configuración",
|
||||
"command.openSettings.keywords": "configuración de preferencias",
|
||||
"command.openLanguageSettings": "Abrir la configuración de idioma",
|
||||
"command.openLanguageSettings.keywords": "idioma configuración regional i18n internacionalización localización inglés italiano francés alemán ruso español portugués chino simplificado tradicional japonés coreano 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "idioma configuración regional i18n internacionalización localización inglés italiano francés alemán ruso español portugués chino simplificado tradicional japonés coreano polaco 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Usar el idioma del sistema",
|
||||
"command.openH1Setting": "Abrir la configuración de renombrado automático de H1",
|
||||
"command.toggleGitignoredFilesVisibility": "Alternar la visibilidad de los archivos Gitignored",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Propiedades",
|
||||
"inspector.title.propertiesShortcut": "Propiedades (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Cerrar Propiedades (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Propiedades en conflicto",
|
||||
"inspector.title.collidingPropertiesAria": "Propiedades en conflicto. Abrir editor sin formato.",
|
||||
"inspector.empty.noNoteSelected": "No se seleccionó ninguna nota",
|
||||
"inspector.empty.noProperties": "Esta nota aún no tiene propiedades",
|
||||
"inspector.empty.initializeProperties": "Inicializar propiedades",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Abrir Ajustes",
|
||||
"command.openSettings.keywords": "preferencias config",
|
||||
"command.openLanguageSettings": "Abrir la configuración de idioma",
|
||||
"command.openLanguageSettings.keywords": "idioma configuración regional i18n internacionalización localización inglés italiano francés alemán ruso español portugués chino simplificado tradicional japonés coreano 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "idioma configuración regional i18n internacionalización localización inglés italiano francés alemán ruso español portugués chino simplificado tradicional japonés coreano polaco 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Usar el idioma del sistema",
|
||||
"command.openH1Setting": "Abrir la configuración de renombrado automático de H1",
|
||||
"command.toggleGitignoredFilesVisibility": "Activar/desactivar la visibilidad de los archivos Gitignored",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Propiedades",
|
||||
"inspector.title.propertiesShortcut": "Propiedades (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Cerrar Propiedades (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Propiedades en conflicto",
|
||||
"inspector.title.collidingPropertiesAria": "Propiedades en conflicto. Abrir editor sin formato.",
|
||||
"inspector.empty.noNoteSelected": "Ninguna nota seleccionada",
|
||||
"inspector.empty.noProperties": "Esta nota aún no tiene propiedades",
|
||||
"inspector.empty.initializeProperties": "Inicializar propiedades",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Ouvrir les paramètres",
|
||||
"command.openSettings.keywords": "préférences config",
|
||||
"command.openLanguageSettings": "Ouvrir les paramètres de langue",
|
||||
"command.openLanguageSettings.keywords": "langue paramètres régionaux i18n internationalisation localisation anglais italien français allemand russe espagnol portugais chinois simplifié traditionnel japonais coréen 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "langue paramètres régionaux i18n internationalisation localisation anglais italien français allemand russe espagnol portugais chinois simplifié traditionnel japonais coréen polonais 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Utiliser la langue du système",
|
||||
"command.openH1Setting": "Ouvrir le paramètre de renommage automatique des titres H1",
|
||||
"command.toggleGitignoredFilesVisibility": "Activer/désactiver la visibilité des fichiers Gitignored",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Propriétés",
|
||||
"inspector.title.propertiesShortcut": "Propriétés (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Fermer les propriétés (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Propriétés en conflit",
|
||||
"inspector.title.collidingPropertiesAria": "Propriétés en conflit. Ouvrir l'éditeur brut.",
|
||||
"inspector.empty.noNoteSelected": "Aucune note sélectionnée",
|
||||
"inspector.empty.noProperties": "Cette note n'a pas encore de propriétés",
|
||||
"inspector.empty.initializeProperties": "Initialiser les propriétés",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Apri le impostazioni",
|
||||
"command.openSettings.keywords": "preferences config",
|
||||
"command.openLanguageSettings": "Apri le impostazioni della lingua",
|
||||
"command.openLanguageSettings.keywords": "lingua locale i18n internazionalizzazione localizzazione inglese italiano francese tedesco russo spagnolo portoghese cinese semplificato tradizionale giapponese coreano 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "lingua locale i18n internazionalizzazione localizzazione inglese italiano francese tedesco russo spagnolo portoghese cinese semplificato tradizionale giapponese coreano polacco 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Usa la lingua di sistema",
|
||||
"command.openH1Setting": "Apri l'impostazione di rinomina automatica H1",
|
||||
"command.toggleGitignoredFilesVisibility": "Attiva/disattiva la visibilità dei file Gitignored",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Proprietà",
|
||||
"inspector.title.propertiesShortcut": "Proprietà (⌘<>⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Chiudi Proprietà (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Proprietà in conflitto",
|
||||
"inspector.title.collidingPropertiesAria": "Proprietà in conflitto. Apri l'editor raw.",
|
||||
"inspector.empty.noNoteSelected": "Nessuna nota selezionata",
|
||||
"inspector.empty.noProperties": "Questa nota non ha ancora proprietà",
|
||||
"inspector.empty.initializeProperties": "Inizializza proprietà",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "設定を開く",
|
||||
"command.openSettings.keywords": "preferences config",
|
||||
"command.openLanguageSettings": "言語設定を開く",
|
||||
"command.openLanguageSettings.keywords": "言語、ロケール、i18n、国際化、ローカライゼーション、英語、イタリア語、フランス語、ドイツ語、ロシア語、スペイン語、ポルトガル語、中国語(簡体)、中国語(繁体)、日本語、韓国語、中文(繁體)、zh-tw",
|
||||
"command.openLanguageSettings.keywords": "言語 ロケール i18n 国際化 ローカライゼーション 英語 イタリア語 フランス語 ドイツ語 ロシア語 スペイン語 ポルトガル語 中国語 簡体字 繁体字 日本語 韓国語 ポーランド語 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "システム言語を使用",
|
||||
"command.openH1Setting": "H1 自動名前変更設定を開く",
|
||||
"command.toggleGitignoredFilesVisibility": "Gitignoredファイルの表示/非表示を切り替える",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "プロパティ",
|
||||
"inspector.title.propertiesShortcut": "プロパティ(⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "プロパティを閉じる(⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "競合するプロパティ",
|
||||
"inspector.title.collidingPropertiesAria": "競合するプロパティ。Rawエディターを開く。",
|
||||
"inspector.empty.noNoteSelected": "ノートが選択されていません",
|
||||
"inspector.empty.noProperties": "このノートにはまだプロパティがありません",
|
||||
"inspector.empty.initializeProperties": "プロパティを初期化",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "설정 열기",
|
||||
"command.openSettings.keywords": "preferences config",
|
||||
"command.openLanguageSettings": "언어 설정 열기",
|
||||
"command.openLanguageSettings.keywords": "언어 로케일 i18n 국제화 현지화 영어 이탈리아어 프랑스어 독일어 러시아어 스페인어 포르투갈어 중국어 간체 중국어 번체 일본어 한국어 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "언어 로케일 i18n 국제화 현지화 영어 이탈리아어 프랑스어 독일어 러시아어 스페인어 포르투갈어 중국어 간체 중국어 번체 일본어 한국어 폴란드어 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "시스템 언어 사용",
|
||||
"command.openH1Setting": "H1 자동 이름 변경 설정 열기",
|
||||
"command.toggleGitignoredFilesVisibility": "Gitignored 파일 표시/숨기기",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "속성",
|
||||
"inspector.title.propertiesShortcut": "속성(⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "속성 닫기 (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "충돌하는 속성",
|
||||
"inspector.title.collidingPropertiesAria": "충돌하는 속성. 원시 편집기를 엽니다.",
|
||||
"inspector.empty.noNoteSelected": "선택한 노트 없음",
|
||||
"inspector.empty.noProperties": "이 노트에는 아직 속성이 없습니다.",
|
||||
"inspector.empty.initializeProperties": "속성 초기화",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Otwórz ustawienia",
|
||||
"command.openSettings.keywords": "preferencje konfiguracja",
|
||||
"command.openLanguageSettings": "Otwórz ustawienia języka",
|
||||
"command.openLanguageSettings.keywords": "język lokalizacja i18n internacjonalizacja angielski włoski francuski niemiecki rosyjski hiszpański portugalski chiński uproszczony tradycyjny japoński koreański polski 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "język ustawienia regionalne i18n internacjonalizacja lokalizacja angielski włoski francuski niemiecki rosyjski hiszpański portugalski chiński uproszczony tradycyjny japoński koreański polski 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Użyj języka systemowego",
|
||||
"command.openH1Setting": "Otwórz ustawienie automatycznej zmiany nazwy H1",
|
||||
"command.toggleGitignoredFilesVisibility": "Przełącz widoczność plików ignorowanych przez Git",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Właściwości",
|
||||
"inspector.title.propertiesShortcut": "Właściwości (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Zamknij Właściwości (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Kolidujące właściwości",
|
||||
"inspector.title.collidingPropertiesAria": "Kolidujące właściwości. Otwórz edytor surowy.",
|
||||
"inspector.empty.noNoteSelected": "Nie wybrano notatki",
|
||||
"inspector.empty.noProperties": "Ta notatka nie ma jeszcze właściwości",
|
||||
"inspector.empty.initializeProperties": "Zainicjalizuj właściwości",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Abrir configurações",
|
||||
"command.openSettings.keywords": "configuração de preferências",
|
||||
"command.openLanguageSettings": "Abrir configurações de idioma",
|
||||
"command.openLanguageSettings.keywords": "idioma localidade i18n internacionalização localização inglês italiano francês alemão russo espanhol português chinês simplificado tradicional japonês coreano 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "idioma localização i18n internacionalização localização inglês italiano francês alemão russo espanhol português chinês simplificado tradicional japonês coreano polonês 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Usar o idioma do sistema",
|
||||
"command.openH1Setting": "Abrir configuração de renomeação automática de H1",
|
||||
"command.toggleGitignoredFilesVisibility": "Alternar visibilidade dos arquivos Gitignored",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Propriedades",
|
||||
"inspector.title.propertiesShortcut": "Propriedades (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Fechar Propriedades (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Propriedades conflitantes",
|
||||
"inspector.title.collidingPropertiesAria": "Propriedades conflitantes. Abrir editor bruto.",
|
||||
"inspector.empty.noNoteSelected": "Nenhuma nota selecionada",
|
||||
"inspector.empty.noProperties": "Esta nota ainda não tem propriedades",
|
||||
"inspector.empty.initializeProperties": "Inicializar propriedades",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Abrir Definições",
|
||||
"command.openSettings.keywords": "configuração de preferências",
|
||||
"command.openLanguageSettings": "Abrir definições de idioma",
|
||||
"command.openLanguageSettings.keywords": "idioma localização i18n internacionalização localização inglês italiano francês alemão russo espanhol português chinês simplificado tradicional japonês coreano 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "idioma região i18n internacionalização localização inglês italiano francês alemão russo espanhol português chinês simplificado tradicional japonês coreano polaco 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Utilizar o idioma do sistema",
|
||||
"command.openH1Setting": "Abrir a definição de renomeação automática de H1",
|
||||
"command.toggleGitignoredFilesVisibility": "Alternar a visibilidade dos ficheiros Gitignored",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Propriedades",
|
||||
"inspector.title.propertiesShortcut": "Propriedades (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Fechar Propriedades (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Propriedades em conflito",
|
||||
"inspector.title.collidingPropertiesAria": "Propriedades em conflito. Abrir editor bruto.",
|
||||
"inspector.empty.noNoteSelected": "Nenhuma nota selecionada",
|
||||
"inspector.empty.noProperties": "Esta nota ainda não tem propriedades",
|
||||
"inspector.empty.initializeProperties": "Inicializar propriedades",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "Открыть настройки",
|
||||
"command.openSettings.keywords": "Настройки конфигурации",
|
||||
"command.openLanguageSettings": "Открыть настройки языка",
|
||||
"command.openLanguageSettings.keywords": "Язык Регион i18n Интернационализация Локализация Английский Итальянский Французский Немецкий Русский Испанский Португальский Китайский (упрощенный) Китайский (традиционный) Японский Корейский 中文 繁體中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "Язык Регион i18n Интернационализация Локализация Английский Итальянский Французский Немецкий Русский Испанский Португальский Китайский (упрощенный) Китайский (традиционный) Японский Корейский Польский 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "Использовать язык системы",
|
||||
"command.openH1Setting": "Открыть настройку автоматического переименования H1",
|
||||
"command.toggleGitignoredFilesVisibility": "Переключить отображение файлов Gitignored",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "Свойства",
|
||||
"inspector.title.propertiesShortcut": "Свойства (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Закрыть свойства (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Конфликтующие свойства",
|
||||
"inspector.title.collidingPropertiesAria": "Конфликтующие свойства. Открыть редактор исходного текста.",
|
||||
"inspector.empty.noNoteSelected": "Ни одна заметка не выбрана",
|
||||
"inspector.empty.noProperties": "У этой заметки пока нет свойств",
|
||||
"inspector.empty.initializeProperties": "Инициализировать свойства",
|
||||
|
||||
@@ -278,6 +278,8 @@
|
||||
"inspector.title.properties": "Thuộc tính",
|
||||
"inspector.title.propertiesShortcut": "Thuộc tính (⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "Đóng thuộc tính (⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "Thuộc tính xung đột",
|
||||
"inspector.title.collidingPropertiesAria": "Thuộc tính xung đột. Mở trình soạn thảo thô.",
|
||||
"inspector.empty.noNoteSelected": "Chưa chọn ghi chú nào",
|
||||
"inspector.empty.noProperties": "Ghi chú này vẫn chưa có thuộc tính",
|
||||
"inspector.empty.initializeProperties": "Khởi tạo thuộc tính",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "打开设置",
|
||||
"command.openSettings.keywords": "设置 偏好 配置",
|
||||
"command.openLanguageSettings": "打开语言设置",
|
||||
"command.openLanguageSettings.keywords": "语言 区域设置 i18n 国际化 本地化 英语 意大利语 法语 德语 俄语 西班牙语 葡萄牙语 中文 简体中文 繁体中文 日语 韩语 中文 繁体中文 zh-tw",
|
||||
"command.openLanguageSettings.keywords": "语言 区域设置 i18n 国际化 本地化 英语 意大利语 法语 德语 俄语 西班牙语 葡萄牙语 中文 简体中文 繁体中文 台湾繁体中文 日语 韩语 波兰语 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "使用系统语言",
|
||||
"command.openH1Setting": "打开 H1 自动重命名设置",
|
||||
"command.toggleGitignoredFilesVisibility": "切换 Gitignored 文件的可见性",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "属性",
|
||||
"inspector.title.propertiesShortcut": "属性(⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "关闭属性(⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "冲突的属性",
|
||||
"inspector.title.collidingPropertiesAria": "冲突的属性。打开原始编辑器。",
|
||||
"inspector.empty.noNoteSelected": "未选择笔记",
|
||||
"inspector.empty.noProperties": "这条笔记还没有属性",
|
||||
"inspector.empty.initializeProperties": "初始化属性",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"command.openSettings": "開啟設定",
|
||||
"command.openSettings.keywords": "設定 偏好 配置",
|
||||
"command.openLanguageSettings": "開啟語言設定",
|
||||
"command.openLanguageSettings.keywords": "語言、地區設定、i18n、國際化、本地化、英語、義大利文、法文、德文、俄文、西班牙文、葡萄牙文、簡體中文、繁體中文、日文、韓文、中文 繁體中文、zh-tw",
|
||||
"command.openLanguageSettings.keywords": "語言 地區設定 i18n 国际化 本地化 英語 意大利語 法語 德語 俄語 西班牙語 葡萄牙語 中文 簡體中文 繁體中文 日語 韓語 波蘭語 中文 繁體中文 zh-tw",
|
||||
"command.useSystemLanguage": "使用系統語言",
|
||||
"command.openH1Setting": "開啟 H1 自動重新命名設定",
|
||||
"command.toggleGitignoredFilesVisibility": "切換 Gitignored 檔案的顯示狀態",
|
||||
@@ -383,6 +383,8 @@
|
||||
"inspector.title.properties": "屬性",
|
||||
"inspector.title.propertiesShortcut": "屬性(⌘⇧I)",
|
||||
"inspector.title.closePropertiesShortcut": "關閉屬性(⌘⇧I)",
|
||||
"inspector.title.collidingProperties": "衝突的屬性",
|
||||
"inspector.title.collidingPropertiesAria": "衝突的屬性。開啟原始編輯器。",
|
||||
"inspector.empty.noNoteSelected": "未選擇筆記",
|
||||
"inspector.empty.noProperties": "這條筆記還沒有屬性",
|
||||
"inspector.empty.initializeProperties": "初始化屬性",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { parseFrontmatter, detectFrontmatterState } from './frontmatter'
|
||||
import { parseFrontmatter, detectFrontmatterState, detectFrontmatterWarnings } from './frontmatter'
|
||||
|
||||
describe('parseFrontmatter', () => {
|
||||
describe('numeric values', () => {
|
||||
@@ -82,6 +82,13 @@ describe('parseFrontmatter', () => {
|
||||
expect(fm['type']).toBe('Note')
|
||||
expect(fm['status']).toBe('Active')
|
||||
})
|
||||
|
||||
it('keeps the last value when frontmatter properties collide', () => {
|
||||
const fm = parseFrontmatter('---\ntype: Note\nstatus: Active\nStatus: Evergreened\n---\n# Title')
|
||||
|
||||
expect(fm['status']).toBeUndefined()
|
||||
expect(fm['Status']).toBe('Evergreened')
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectFrontmatterState', () => {
|
||||
@@ -121,3 +128,13 @@ describe('detectFrontmatterState', () => {
|
||||
expect(detectFrontmatterState('---\n{broken: [yaml\n---\nBody')).toBe('invalid')
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectFrontmatterWarnings', () => {
|
||||
it('reports colliding frontmatter properties', () => {
|
||||
const warnings = detectFrontmatterWarnings('---\ntype: Note\nstatus: Active\nStatus: Evergreened\n---\n# Title')
|
||||
|
||||
expect(warnings.collidingProperties).toEqual([
|
||||
{ key: 'status', labels: ['status', 'Status'] },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { FrontmatterValue } from '../components/Inspector'
|
||||
import { canonicalSystemMetadataKey, normalizePropertyKey } from './systemMetadata'
|
||||
|
||||
export interface ParsedFrontmatter {
|
||||
[key: string]: FrontmatterValue
|
||||
@@ -10,6 +11,15 @@ type FrontmatterLine = string
|
||||
type FrontmatterKey = string
|
||||
type FrontmatterText = string
|
||||
|
||||
export interface FrontmatterCollisionWarning {
|
||||
key: string
|
||||
labels: string[]
|
||||
}
|
||||
|
||||
export interface FrontmatterWarnings {
|
||||
collidingProperties: FrontmatterCollisionWarning[]
|
||||
}
|
||||
|
||||
const FRONTMATTER_CLOSE_DELIMITER = /(?:^|\r?\n)---(?:\r?\n|$)/
|
||||
|
||||
function unquote(s: FrontmatterText): FrontmatterText {
|
||||
@@ -86,6 +96,45 @@ function parseKeyValueLine(line: FrontmatterLine): { key: FrontmatterKey, value:
|
||||
}
|
||||
}
|
||||
|
||||
function parseTopLevelKey(line: FrontmatterLine): FrontmatterKey | null {
|
||||
if (line.trim() === '' || line.startsWith(' ') || line.startsWith('\t')) return null
|
||||
return parseKeyValueLine(line)?.key ?? null
|
||||
}
|
||||
|
||||
const FRONTMATTER_COLLISION_ALIASES: Record<string, string> = {
|
||||
is_a: 'type',
|
||||
archived: '_archived',
|
||||
}
|
||||
|
||||
function frontmatterCollisionKey(key: FrontmatterKey): FrontmatterKey {
|
||||
const normalized = normalizePropertyKey(canonicalSystemMetadataKey(key))
|
||||
return FRONTMATTER_COLLISION_ALIASES[normalized] ?? normalized
|
||||
}
|
||||
|
||||
function addCollisionCandidate(
|
||||
groups: Map<FrontmatterKey, { labels: FrontmatterKey[]; count: number }>,
|
||||
key: FrontmatterKey,
|
||||
) {
|
||||
const collisionKey = frontmatterCollisionKey(key)
|
||||
const group = groups.get(collisionKey) ?? { labels: [], count: 0 }
|
||||
group.count += 1
|
||||
if (!group.labels.includes(key)) group.labels.push(key)
|
||||
groups.set(collisionKey, group)
|
||||
}
|
||||
|
||||
function assignFrontmatterValue(
|
||||
result: ParsedFrontmatter,
|
||||
collisionKeys: Map<FrontmatterKey, FrontmatterKey>,
|
||||
key: FrontmatterKey,
|
||||
value: FrontmatterValue,
|
||||
) {
|
||||
const collisionKey = frontmatterCollisionKey(key)
|
||||
const previousKey = collisionKeys.get(collisionKey)
|
||||
if (previousKey && previousKey !== key) delete result[previousKey]
|
||||
collisionKeys.set(collisionKey, key)
|
||||
result[key] = value
|
||||
}
|
||||
|
||||
function parseFrontmatterValue(value: FrontmatterText): FrontmatterValue | undefined {
|
||||
if (isBlockScalar(value)) return undefined
|
||||
if (isInlineArrayLiteral(value)) return parseInlineArray(value)
|
||||
@@ -94,11 +143,12 @@ function parseFrontmatterValue(value: FrontmatterText): FrontmatterValue | undef
|
||||
|
||||
function flushList(
|
||||
result: ParsedFrontmatter,
|
||||
collisionKeys: Map<FrontmatterKey, FrontmatterKey>,
|
||||
currentKey: FrontmatterKey | null,
|
||||
currentList: FrontmatterText[],
|
||||
): FrontmatterText[] {
|
||||
if (currentKey && currentList.length > 0) {
|
||||
result[currentKey] = collapseList(currentList)
|
||||
assignFrontmatterValue(result, collisionKeys, currentKey, collapseList(currentList))
|
||||
}
|
||||
return []
|
||||
}
|
||||
@@ -109,6 +159,7 @@ export function parseFrontmatter(content: MarkdownContent | null): ParsedFrontma
|
||||
if (frontmatterBody === null) return {}
|
||||
|
||||
const result: ParsedFrontmatter = {}
|
||||
const collisionKeys = new Map<FrontmatterKey, FrontmatterKey>()
|
||||
let currentKey: FrontmatterKey | null = null
|
||||
let currentList: FrontmatterText[] = []
|
||||
|
||||
@@ -119,7 +170,7 @@ export function parseFrontmatter(content: MarkdownContent | null): ParsedFrontma
|
||||
continue
|
||||
}
|
||||
|
||||
currentList = flushList(result, currentKey, currentList)
|
||||
currentList = flushList(result, collisionKeys, currentKey, currentList)
|
||||
|
||||
const keyValue = parseKeyValueLine(line)
|
||||
if (!keyValue) continue
|
||||
@@ -127,10 +178,31 @@ export function parseFrontmatter(content: MarkdownContent | null): ParsedFrontma
|
||||
|
||||
const parsedValue = parseFrontmatterValue(keyValue.value)
|
||||
if (parsedValue !== undefined) {
|
||||
result[currentKey] = parsedValue
|
||||
assignFrontmatterValue(result, collisionKeys, currentKey, parsedValue)
|
||||
}
|
||||
}
|
||||
|
||||
flushList(result, currentKey, currentList)
|
||||
flushList(result, collisionKeys, currentKey, currentList)
|
||||
return result
|
||||
}
|
||||
|
||||
export function detectFrontmatterWarnings(content: MarkdownContent | null): FrontmatterWarnings {
|
||||
const frontmatterBody = extractFrontmatterBody(content)
|
||||
if (frontmatterBody === null) return { collidingProperties: [] }
|
||||
|
||||
const groups = new Map<FrontmatterKey, { labels: FrontmatterKey[]; count: number }>()
|
||||
for (const line of frontmatterBody.split(/\r?\n/)) {
|
||||
const key = parseTopLevelKey(line)
|
||||
if (key) addCollisionCandidate(groups, key)
|
||||
}
|
||||
|
||||
const collidingProperties = Array.from(groups.entries())
|
||||
.filter(([, group]) => group.count > 1)
|
||||
.map(([key, group]) => ({ key, labels: group.labels }))
|
||||
|
||||
return { collidingProperties }
|
||||
}
|
||||
|
||||
export function hasFrontmatterWarnings(warnings: FrontmatterWarnings): boolean {
|
||||
return warnings.collidingProperties.length > 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user