diff --git a/CHANGELOG.md b/CHANGELOG.md index 673dcd2..e8ed39d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Replacement-workflow reliability and file navigation. - Hardened folder-tree image handling so image assets cannot enter the editable-document `read-file`/grant path. Image rows remain selectable and can be revealed in Finder through the existing folder-scoped authority instead of being opened as Markdown. - Added **Export with Previous** as per-document, session-only export history. It remembers only the last successfully completed export format and reruns the normal save flow; cancelled/failed exports do not replace history, and output destinations are not persisted or silently reused. - Hardened Electron local-image preview/export reads against pathname replacement: image bytes are now read from a bound file descriptor only after its filesystem identity and containment are validated, with symlink and size checks retained. +- Added renderer-only file-library exclusion globs for Files, Articles and Open Quickly. Patterns are stored as preference text only, never as filesystem authority; the active/revealed document remains visible and Search in Folder continues to search the full authorized folder. ## 0.1.6 — 2026-10-10 diff --git a/README.md b/README.md index 66cfc4a..d12c7d4 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ This project is an independent clean-room implementation. It does not contain Ty - Folder file tree, article list and document outline; the native runtime can create/rename/trash files and folders, refresh the tree, sort by name, modification time or creation time, optionally show hidden files, and reveal selected assets in Finder without promoting images into editable-document authority. - Multi-document tabs with per-document dirty state, Save All, safe close prompts and next/previous tab navigation. - Native recent documents, reopen-closed history, Open Quickly, Reveal in File Tree and Open File Location. +- Files, Articles and Open Quickly support optional relative exclusion globs for hiding generated or noisy entries without changing folder authority or Search in Folder. - Explicit **Restore Previous Session** for clean saved tabs; missing, replaced or symlinked paths are skipped, and no folder grant is restored implicitly. - Duplicate, Rename and Move To for saved local documents, including complete TextBundle package operations. - Editable TextBundle packages (`.textbundle`) with payload-preserving saves and bundle-local assets. diff --git a/docs/PARITY_MATRIX.md b/docs/PARITY_MATRIX.md index 888c76f..ff68730 100644 --- a/docs/PARITY_MATRIX.md +++ b/docs/PARITY_MATRIX.md @@ -11,7 +11,7 @@ Reference inspected locally with REA: Typora 1.14.10 build 7786 on macOS. Primar | Live rendered Markdown editing | shipped document/editor surfaces | Implemented | MDXEditor/Lexical, Markdown in/out, no proprietary editor code | | Markdown/text files | plist document registrations | Implemented | Open/save/save-as and macOS file associations | | TextBundle packages | plist editable TextBundle document role | Implemented | `.textbundle` is a macOS package association; payload-only saves preserve metadata/assets and Save As can create a new bundle | -| Open folder / file tree | static sidebar/file-library resources | Partial | Recursive Markdown/text tree and article list; native runtime adds create file/folder, rename, Move to Trash, refresh, name/modified/creation-date sorting and an opt-in host-enforced hidden-file visibility preference. Configurable glob filtering, pinning, grouping and other library controls remain | +| Open folder / file tree | static sidebar/file-library resources | Partial | Recursive Markdown/text tree and article list; native runtime adds create file/folder, rename, Move to Trash, refresh, name/modified/creation-date sorting and an opt-in host-enforced hidden-file visibility preference. Renderer-only relative exclusion globs can hide entries from Files/Articles/Open Quickly without changing folder authority or Search in Folder; pinning, grouping and other library controls remain | | Recent/reopen/quick file workflows | static File menu resources | Implemented | Native recent documents, renderer closed-file history, and filename-only quick open over the current folder | | Duplicate / Rename / Move To | static File menu resources | Implemented | Saved local documents only; tab paths update after filesystem operations and TextBundle packages move/copy as complete directories | | Outline | static Outline resources | Implemented | H1-H6 extraction/navigation, flat or hierarchical presentation, configurable maximum heading level, hierarchical branch collapse/expand, Expand All / Collapse All and current-section highlighting/reveal are implemented. Hidden deeper active headings project to their nearest visible ancestor | @@ -75,7 +75,7 @@ These are intentionally tracked even when they are not part of the current imple | Next | Configurable outer-whitespace behavior | Explicit hard-break semantics are preserved and smart punctuation now delegates to the host's explicit native substitution controls. MDXEditor still trims outer document whitespace during root import/export even with `trim={false}`; preserving it would require an explicit conservative editor-state contract rather than an invisible TypeOpen-owned envelope | | Later | Folder-session hints and stronger recovery/version workflows | Clean saved tabs now restore explicitly after native identity revalidation. Folder grants remain deliberately non-persistent; any future folder hint must still require a normal user-authorized Open Folder flow | | Later | Multi-window / Open in New Window | Multi-tab model exists; the native app still centers on one main editing window | -| Later | Advanced file library: configurable glob patterns, pinned/recent locations and grouping | Native management, name/modified/creation-date sorting and a host-enforced hidden-file toggle exist | +| Later | Advanced file library: pinned/recent locations and grouping | Native management, name/modified/creation-date sorting, a host-enforced hidden-file toggle and renderer-only relative exclusion globs exist. Persisted folder locations remain deferred because they must not imply restored filesystem authority | | Later | Advanced image management: configurable asset roots/copy rules, copy/move/rename/delete image assets, syntax switching, zoom/open actions | Safe local image insert/preview and adjacent `assets/` storage exist. Folder-tree image assets are kept out of the editable-document grant path; Electron preview/export reads bind validated image identity to the opened descriptor rather than reopening a pathname. Configurable write roots/copy rules remain deferred pending stronger destination-directory semantics; cloud/PicGo/PicList integration is intentionally deferred | | Later | Export profiles: headers/footers, heading page breaks, export theme/CSS/templates, destination/post-export actions and richer Pandoc arguments | Core native/Pandoc export plus **Export with Previous** exist. Previous export history is per-document and session-only, stores only the last successful format, and always returns through the normal destination picker; overwrite/destination reuse remains intentionally deferred pending an explicit authority model | | Later | Custom shortcuts, richer spellcheck language controls, separate light/dark custom themes and launch behavior preferences | Not required for core Markdown replacement but retained as parity backlog | diff --git a/src/App.tsx b/src/App.tsx index 4b4e2ec..fa2308c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,6 +16,7 @@ import { buildSessionIntent } from './lib/sessionRestore' import { codeFenceSnippet, effectiveCodeBlockLanguage, normalizeCodeBlockLanguage, type CodeBlockLanguage } from './lib/codeLanguages' import { normalizeOutlineMaxLevel, normalizeOutlineMode } from './lib/outlinePreferences' import { isExportFormat, rememberSuccessfulExport, type ExportFormat } from './lib/exportHistory' +import { filterFileTreeByGlobs } from './lib/fileTreeFilters' const EMPTY_DOCUMENT = '' const PREF_KEY = 'typeopen.preferences.v1' @@ -40,7 +41,8 @@ const DEFAULT_PREFS: TypeOpenPreferences = { statusBar: true, toolbar: false, sidebarDefault: true, - showHiddenFiles: false + showHiddenFiles: false, + fileTreeExcludeGlobs: '' } function loadPageSetup(): PageSetupState { @@ -62,7 +64,8 @@ function loadPreferences(): TypeOpenPreferences { ...parsed, defaultCodeLanguage: normalizeCodeBlockLanguage(parsed?.defaultCodeLanguage), outlineMode: normalizeOutlineMode(parsed?.outlineMode), - outlineMaxLevel: normalizeOutlineMaxLevel(parsed?.outlineMaxLevel) + outlineMaxLevel: normalizeOutlineMaxLevel(parsed?.outlineMaxLevel), + fileTreeExcludeGlobs: typeof parsed?.fileTreeExcludeGlobs === 'string' ? parsed.fileTreeExcludeGlobs.slice(0, 4096) : '' } } catch { return DEFAULT_PREFS @@ -187,6 +190,7 @@ export default function App() { const filePath = activeDocument?.filePath ?? null const dirty = activeDocument ? isDocumentDirty(activeDocument) : false const hasDirtyDocuments = documents.some(isDocumentDirty) + const visibleFileTree = useMemo(() => filterFileTreeByGlobs(fileTree, folderPath, preferences.fileTreeExcludeGlobs, [filePath, fileReveal?.path]), [filePath, fileReveal?.path, fileTree, folderPath, preferences.fileTreeExcludeGlobs]) const outline = useMemo(() => extractOutline(markdown), [markdown]) const stats = useMemo(() => getDocumentStats(markdown), [markdown]) const searchResult = useMemo(() => searchText(markdown, findQuery, { matchCase, wholeWord, regex: regexSearch }), [findQuery, markdown, matchCase, regexSearch, wholeWord]) @@ -1146,7 +1150,7 @@ export default function App() {
- void loadFile(path)} onRevealEntry={(node) => void revealFolderEntryInFinder(node)} onHeadingClick={scrollToHeading} onRequestFocusEditor={() => editorRef.current?.focus()} folderPath={folderPath} onCreateEntry={beginCreateFolderEntry} onRenameEntry={beginRenameFolderEntry} onTrashEntry={(node) => void trashFolderEntry(node)} onRefreshFolder={() => { if (folderPath) void refreshFolder(folderPath) }} /> + void loadFile(path)} onRevealEntry={(node) => void revealFolderEntryInFinder(node)} onHeadingClick={scrollToHeading} onRequestFocusEditor={() => editorRef.current?.focus()} folderPath={folderPath} onCreateEntry={beginCreateFolderEntry} onRenameEntry={beginRenameFolderEntry} onTrashEntry={(node) => void trashFolderEntry(node)} onRefreshFolder={() => { if (folderPath) void refreshFolder(folderPath) }} />
setGlobalSearchVisible(false)} onOpenResult={openGlobalSearchResult} /> - setQuickOpenVisible(false)} onOpenFile={loadFile} /> + setQuickOpenVisible(false)} onOpenFile={loadFile} /> +