feat: MVP-5 P2 - Export/Import, Settings, Tests y Validaciones

- Ticket 10: Navegación completa de listas por teclado (↑↓ Enter E F P)
- Ticket 13: Historial de navegación contextual con recent-context-list
- Ticket 17: Exportación mejorada a Markdown con frontmatter
- Ticket 18: Exportación HTML simple y legible
- Ticket 19: Importador Markdown mejorado con frontmatter, tags, wiki links
- Ticket 20: Importador Obsidian-compatible (wiki links, #tags inline)
- Ticket 21: Centro de respaldo y portabilidad en Settings
- Ticket 22: Configuración visible de feature flags
- Ticket 24: Tests de command palette y captura externa
- Ticket 25: Harden de validaciones y límites (50MB backup, 10K notas, etc)
This commit is contained in:
2026-03-22 19:39:55 -03:00
parent 8d56f34d68
commit e66a678160
24 changed files with 1286 additions and 42 deletions
+40 -3
View File
@@ -4,11 +4,48 @@ export interface CapturePayload {
selection: string
}
// Limits for capture payloads
const MAX_TITLE_LENGTH = 500
const MAX_URL_LENGTH = 2000
const MAX_SELECTION_LENGTH = 10000
export interface CaptureValidationResult {
valid: boolean
errors: string[]
}
export function validateCapturePayload(payload: CapturePayload): CaptureValidationResult {
const errors: string[] = []
if (!payload.title || typeof payload.title !== 'string') {
errors.push('Title is required')
} else if (payload.title.length > MAX_TITLE_LENGTH) {
errors.push(`Title too long: ${payload.title.length} chars (max: ${MAX_TITLE_LENGTH})`)
}
if (payload.url && typeof payload.url !== 'string') {
errors.push('URL must be a string')
} else if (payload.url && payload.url.length > MAX_URL_LENGTH) {
errors.push(`URL too long: ${payload.url.length} chars (max: ${MAX_URL_LENGTH})`)
}
if (payload.selection && typeof payload.selection !== 'string') {
errors.push('Selection must be a string')
} else if (payload.selection && payload.selection.length > MAX_SELECTION_LENGTH) {
errors.push(`Selection too long: ${payload.selection.length} chars (max: ${MAX_SELECTION_LENGTH})`)
}
return {
valid: errors.length === 0,
errors,
}
}
export function encodeCapturePayload(payload: CapturePayload): string {
const params = new URLSearchParams({
title: payload.title,
url: payload.url,
selection: payload.selection,
title: payload.title.slice(0, MAX_TITLE_LENGTH),
url: payload.url.slice(0, MAX_URL_LENGTH),
selection: payload.selection.slice(0, MAX_SELECTION_LENGTH),
})
return params.toString()
}