Feat v0.4.6 sidebar restructure (#28)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #28.
This commit is contained in:
30
CHANGELOG.md
30
CHANGELOG.md
@@ -2,6 +2,36 @@
|
|||||||
|
|
||||||
All notable changes to Switchboard Core are documented here.
|
All notable changes to Switchboard Core are documented here.
|
||||||
|
|
||||||
|
## v0.4.6 — Sidebar Restructure
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Sidebar tabs**: Two-tab header ("Notes" / "Outline") replaces the static
|
||||||
|
sidebar header. Notes tab contains folder tree, tag filter, search, and note
|
||||||
|
list. Outline tab shows the document heading tree for the active note.
|
||||||
|
Outline tab disabled (grayed) when no note is selected.
|
||||||
|
- **Sidebar outline**: Document outline relocated from editor-side panel into
|
||||||
|
the sidebar's Outline tab. Nested heading hierarchy (H1→H2→H3) with depth
|
||||||
|
indentation via `padding-left`. Click heading scrolls to it in both preview
|
||||||
|
and CM6 editor modes.
|
||||||
|
- **Active heading tracking**: Scroll listener (RAF-throttled) highlights the
|
||||||
|
current heading in the sidebar outline based on scroll position. Uses
|
||||||
|
`getBoundingClientRect()` in preview/split mode and CM6 viewport line in
|
||||||
|
edit mode.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Notes package version bumped from 0.6.0 to 0.7.0.
|
||||||
|
- `EditorPane` exposes headings, scroll function, and active heading index to
|
||||||
|
parent via callback props (`onHeadingsChange`, `scrollToHeadingRef`,
|
||||||
|
`onActiveHeadingChange`).
|
||||||
|
- Sidebar auto-resets to Notes tab when active note is deleted.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Old `DocumentOutline` component and `.doc-outline` CSS styles (replaced by
|
||||||
|
sidebar outline).
|
||||||
|
|
||||||
## v0.4.5 — Editor Modes + Document Outline
|
## v0.4.5 — Editor Modes + Document Outline
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -301,12 +301,12 @@ Zero platform special-casing. Proves the full extension stack E2E.
|
|||||||
| View mode setting | ✅ | `editor_mode` in manifest settings + localStorage persistence. Toolbar cycles rendered → edit → split. |
|
| View mode setting | ✅ | `editor_mode` in manifest settings + localStorage persistence. Toolbar cycles rendered → edit → split. |
|
||||||
| Document outline | ✅ | `parseHeadings()` extracts headings (skips code blocks). Collapsible `DocumentOutline` panel with click-to-scroll (CM6 line dispatch or preview `scrollIntoView`). Debounced 300ms updates. |
|
| Document outline | ✅ | `parseHeadings()` extracts headings (skips code blocks). Collapsible `DocumentOutline` panel with click-to-scroll (CM6 line dispatch or preview `scrollIntoView`). Debounced 300ms updates. |
|
||||||
|
|
||||||
### v0.4.6 — Sidebar Restructure (planned)
|
### v0.4.6 — Sidebar Restructure (complete)
|
||||||
|
|
||||||
| Step | Status | Description |
|
| Step | Status | Description |
|
||||||
|------|--------|-------------|
|
|------|--------|-------------|
|
||||||
| Sidebar tabs | | Two-tab header at top of sidebar: "Notes" (folders → tags → search → list) and "Outline" (heading tree for active note). Outline tab enabled only when a note is selected. |
|
| Sidebar tabs | ✅ | Two-tab header at top of sidebar: "Notes" (folders → tags → search → list) and "Outline" (heading tree for active note). Outline tab enabled only when a note is selected. |
|
||||||
| Heading tree | | Nested heading hierarchy (H1 → H2 → H3) with depth indentation. Click → scroll to heading in editor/preview. Current heading highlighted based on scroll position. |
|
| Heading tree | ✅ | Nested heading hierarchy (H1 → H2 → H3) with depth indentation. Click → scroll to heading in editor/preview. Current heading highlighted based on scroll position. |
|
||||||
|
|
||||||
### v0.4.7 — Note Graph (planned)
|
### v0.4.7 — Note Graph (planned)
|
||||||
|
|
||||||
|
|||||||
@@ -632,57 +632,79 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Document Outline ─────────────────────── */
|
/* ── Sidebar Tabs ────────────────────────── */
|
||||||
.doc-outline {
|
.sidebar-tabs {
|
||||||
width: 200px;
|
display: flex;
|
||||||
min-width: 160px;
|
border-bottom: 1px solid var(--border);
|
||||||
border-left: 1px solid var(--border);
|
|
||||||
background: var(--bg-raised);
|
|
||||||
overflow-y: auto;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.doc-outline__header {
|
.sidebar-tabs__tab {
|
||||||
font-size: 11px;
|
flex: 1;
|
||||||
font-weight: 600;
|
padding: 10px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
color: var(--text-2);
|
color: var(--text-2);
|
||||||
text-transform: uppercase;
|
background: none;
|
||||||
letter-spacing: 0.5px;
|
border: none;
|
||||||
padding: 8px 10px;
|
border-bottom: 2px solid transparent;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
transition: var(--transition);
|
||||||
border-bottom: 1px solid var(--border);
|
text-align: center;
|
||||||
|
font-family: var(--font);
|
||||||
}
|
}
|
||||||
.doc-outline__toggle {
|
.sidebar-tabs__tab:hover {
|
||||||
font-size: 9px;
|
color: var(--text);
|
||||||
color: var(--text-2);
|
|
||||||
}
|
|
||||||
.doc-outline__count {
|
|
||||||
background: var(--bg-hover);
|
background: var(--bg-hover);
|
||||||
color: var(--text-2);
|
|
||||||
font-size: 10px;
|
|
||||||
padding: 0 6px;
|
|
||||||
border-radius: 8px;
|
|
||||||
line-height: 16px;
|
|
||||||
}
|
}
|
||||||
.doc-outline__item {
|
.sidebar-tabs__tab--active {
|
||||||
padding: 4px 10px;
|
color: var(--text);
|
||||||
font-size: 12px;
|
border-bottom-color: var(--accent);
|
||||||
|
}
|
||||||
|
.sidebar-tabs__tab--disabled {
|
||||||
|
color: var(--text-3);
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.sidebar-tabs__tab--disabled:hover {
|
||||||
|
background: none;
|
||||||
|
color: var(--text-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Sidebar Outline ─────────────────────── */
|
||||||
|
.sidebar-outline {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
.sidebar-outline__empty {
|
||||||
|
padding: 40px 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-3);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.sidebar-outline__item {
|
||||||
|
padding: 5px 12px;
|
||||||
|
font-size: 13px;
|
||||||
color: var(--text-2);
|
color: var(--text-2);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
transition: var(--transition);
|
transition: var(--transition);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
margin: 1px 6px;
|
||||||
}
|
}
|
||||||
.doc-outline__item:hover {
|
.sidebar-outline__item:hover {
|
||||||
background: var(--bg-hover);
|
background: var(--bg-hover);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
.doc-outline__item--h1 { font-weight: 600; color: var(--text); }
|
.sidebar-outline__item--active {
|
||||||
.doc-outline__item--h2 { font-weight: 500; }
|
background: var(--bg-active);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.sidebar-outline__item--h1 { font-weight: 600; color: var(--text); }
|
||||||
|
.sidebar-outline__item--h2 { font-weight: 500; }
|
||||||
|
|
||||||
/* ── Responsive ──────────────────────────── */
|
/* ── Responsive ──────────────────────────── */
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
@@ -691,5 +713,4 @@
|
|||||||
.folder-tree { max-height: 30vh; }
|
.folder-tree { max-height: 30vh; }
|
||||||
.notes-editor__body--split { grid-template-columns: 1fr; }
|
.notes-editor__body--split { grid-template-columns: 1fr; }
|
||||||
.notes-editor__body--split .notes-preview { display: none; }
|
.notes-editor__body--split .notes-preview { display: none; }
|
||||||
.doc-outline { display: none; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Notes — Surface Entry Point (v0.6.0)
|
* Notes — Surface Entry Point (v0.7.0)
|
||||||
*
|
*
|
||||||
* Markdown notes surface using the SDK:
|
* Markdown notes surface using the SDK:
|
||||||
* sw.api.ext('notes') — scoped API client
|
* sw.api.ext('notes') — scoped API client
|
||||||
@@ -628,44 +628,38 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DocumentOutline({ headings, viewMode, cmEditorRef, previewRef }) {
|
// ═══════════════════════════════════════════
|
||||||
var [collapsed, setCollapsed] = useState(false);
|
// SidebarTabs — Notes / Outline tab header
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
if (!headings || headings.length === 0) return null;
|
function SidebarTabs({ activeTab, onTabChange, noteSelected }) {
|
||||||
|
return html`
|
||||||
|
<div class="sidebar-tabs">
|
||||||
|
<button class="sidebar-tabs__tab ${activeTab === 'notes' ? 'sidebar-tabs__tab--active' : ''}"
|
||||||
|
onClick=${function() { onTabChange('notes'); }}>Notes</button>
|
||||||
|
<button class="sidebar-tabs__tab ${activeTab === 'outline' ? 'sidebar-tabs__tab--active' : ''} ${!noteSelected ? 'sidebar-tabs__tab--disabled' : ''}"
|
||||||
|
onClick=${function() { if (noteSelected) onTabChange('outline'); }}>Outline</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function scrollToHeading(heading) {
|
|
||||||
// scroll preview in rendered / split
|
// ═══════════════════════════════════════════
|
||||||
if (viewMode !== 'edit' && previewRef.current) {
|
// SidebarOutline — heading tree in sidebar
|
||||||
var targets = previewRef.current.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
// ═══════════════════════════════════════════
|
||||||
for (var i = 0; i < targets.length; i++) {
|
|
||||||
if (targets[i].textContent.trim() === heading.text) {
|
function SidebarOutline({ headings, activeHeadingIdx, onScrollToHeading }) {
|
||||||
targets[i].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
if (!headings || headings.length === 0) {
|
||||||
return;
|
return html`<div class="sidebar-outline__empty">No headings found</div>`;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// scroll CM6 in edit / split
|
|
||||||
if (viewMode !== 'rendered' && cmEditorRef.current) {
|
|
||||||
var view = cmEditorRef.current.getView();
|
|
||||||
if (!view) return;
|
|
||||||
var lineNum = Math.min(heading.line + 1, view.state.doc.lines);
|
|
||||||
var lineObj = view.state.doc.line(lineNum);
|
|
||||||
view.dispatch({ effects: CM.EditorView.scrollIntoView(lineObj.from, { y: 'start' }) });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="doc-outline">
|
<div class="sidebar-outline">
|
||||||
<div class="doc-outline__header" onClick=${function() { setCollapsed(!collapsed); }}>
|
${headings.map(function(h, i) {
|
||||||
<span class="doc-outline__toggle">${collapsed ? '\u25B6' : '\u25BC'}</span>
|
|
||||||
<span>Outline</span>
|
|
||||||
<span class="doc-outline__count">${headings.length}</span>
|
|
||||||
</div>
|
|
||||||
${!collapsed && headings.map(function(h, i) {
|
|
||||||
return html`
|
return html`
|
||||||
<div key=${i} class="doc-outline__item doc-outline__item--h${h.level}"
|
<div key=${i} class="sidebar-outline__item sidebar-outline__item--h${h.level} ${i === activeHeadingIdx ? 'sidebar-outline__item--active' : ''}"
|
||||||
style="padding-left: ${8 + (h.level - 1) * 12}px"
|
style="padding-left: ${10 + (h.level - 1) * 14}px"
|
||||||
onClick=${function() { scrollToHeading(h); }}>
|
onClick=${function() { onScrollToHeading(h); }}>
|
||||||
${h.text}
|
${h.text}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -676,7 +670,7 @@
|
|||||||
|
|
||||||
var _modeStore = sw.storage.local('notes');
|
var _modeStore = sw.storage.local('notes');
|
||||||
|
|
||||||
function EditorPane({ note, folders, allTags, onSave, onDelete, onRefresh, onTagsChanged, onNavigate, onNavigateToTitle }) {
|
function EditorPane({ note, folders, allTags, onSave, onDelete, onRefresh, onTagsChanged, onNavigate, onNavigateToTitle, onHeadingsChange, scrollToHeadingRef, onActiveHeadingChange }) {
|
||||||
var [title, setTitle] = useState(note ? note.title : '');
|
var [title, setTitle] = useState(note ? note.title : '');
|
||||||
var [body, setBody] = useState(note ? note.body : '');
|
var [body, setBody] = useState(note ? note.body : '');
|
||||||
var [noteTags, setNoteTags] = useState([]);
|
var [noteTags, setNoteTags] = useState([]);
|
||||||
@@ -914,10 +908,82 @@
|
|||||||
var [headings, setHeadings] = useState([]);
|
var [headings, setHeadings] = useState([]);
|
||||||
useEffect(function() {
|
useEffect(function() {
|
||||||
debounce('outline', function() {
|
debounce('outline', function() {
|
||||||
setHeadings(parseHeadings(body));
|
var h = parseHeadings(body);
|
||||||
|
setHeadings(h);
|
||||||
|
if (onHeadingsChange) onHeadingsChange(h);
|
||||||
}, 300);
|
}, 300);
|
||||||
}, [body]);
|
}, [body]);
|
||||||
|
|
||||||
|
// ── Expose scrollToHeading to parent via ref ──
|
||||||
|
useEffect(function() {
|
||||||
|
if (!scrollToHeadingRef) return;
|
||||||
|
scrollToHeadingRef.current = function(heading) {
|
||||||
|
// scroll preview in rendered / split
|
||||||
|
if (viewMode !== 'edit' && previewRef.current) {
|
||||||
|
var targets = previewRef.current.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
||||||
|
for (var i = 0; i < targets.length; i++) {
|
||||||
|
if (targets[i].textContent.trim() === heading.text) {
|
||||||
|
targets[i].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// scroll CM6 in edit / split
|
||||||
|
if (viewMode !== 'rendered' && cmEditorRef.current) {
|
||||||
|
var view = cmEditorRef.current.getView();
|
||||||
|
if (!view) return;
|
||||||
|
var lineNum = Math.min(heading.line + 1, view.state.doc.lines);
|
||||||
|
var lineObj = view.state.doc.line(lineNum);
|
||||||
|
view.dispatch({ effects: CM.EditorView.scrollIntoView(lineObj.from, { y: 'start' }) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [viewMode]);
|
||||||
|
|
||||||
|
// ── Active heading tracking on scroll ──
|
||||||
|
useEffect(function() {
|
||||||
|
if (!onActiveHeadingChange || !note) return;
|
||||||
|
var scrollEl = null;
|
||||||
|
var usePreview = viewMode !== 'edit';
|
||||||
|
if (usePreview && previewRef.current) {
|
||||||
|
scrollEl = previewRef.current;
|
||||||
|
} else if (viewMode !== 'rendered' && cmEditorRef.current) {
|
||||||
|
var view = cmEditorRef.current.getView();
|
||||||
|
if (view) scrollEl = view.scrollDOM;
|
||||||
|
}
|
||||||
|
if (!scrollEl) return;
|
||||||
|
|
||||||
|
var ticking = false;
|
||||||
|
function onScroll() {
|
||||||
|
if (ticking) return;
|
||||||
|
ticking = true;
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
ticking = false;
|
||||||
|
if (usePreview && previewRef.current) {
|
||||||
|
var els = previewRef.current.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
||||||
|
var active = -1;
|
||||||
|
for (var i = 0; i < els.length; i++) {
|
||||||
|
if (els[i].getBoundingClientRect().top <= previewRef.current.getBoundingClientRect().top + 30) {
|
||||||
|
active = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onActiveHeadingChange(active);
|
||||||
|
} else if (cmEditorRef.current) {
|
||||||
|
var v = cmEditorRef.current.getView();
|
||||||
|
if (!v) return;
|
||||||
|
var topLine = v.state.doc.lineAt(v.viewport.from).number - 1;
|
||||||
|
var active = -1;
|
||||||
|
for (var i = 0; i < headings.length; i++) {
|
||||||
|
if (headings[i].line <= topLine) active = i;
|
||||||
|
}
|
||||||
|
onActiveHeadingChange(active);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
scrollEl.addEventListener('scroll', onScroll);
|
||||||
|
onScroll(); // initial check
|
||||||
|
return function() { scrollEl.removeEventListener('scroll', onScroll); };
|
||||||
|
}, [viewMode, note ? note.id : null, headings]);
|
||||||
|
|
||||||
// build indented folder list for the select dropdown
|
// build indented folder list for the select dropdown
|
||||||
var folderOptions = useMemo(function() {
|
var folderOptions = useMemo(function() {
|
||||||
var list = folders || [];
|
var list = folders || [];
|
||||||
@@ -1025,8 +1091,6 @@
|
|||||||
<div class="notes-editor__body ${viewMode === 'split' ? 'notes-editor__body--split' : ''}">
|
<div class="notes-editor__body ${viewMode === 'split' ? 'notes-editor__body--split' : ''}">
|
||||||
${renderEditorBody()}
|
${renderEditorBody()}
|
||||||
</div>
|
</div>
|
||||||
<${DocumentOutline} headings=${headings} viewMode=${viewMode}
|
|
||||||
cmEditorRef=${cmEditorRef} previewRef=${previewRef} />
|
|
||||||
</div>
|
</div>
|
||||||
<${BacklinksPanel} noteId=${note.id} onNavigate=${onNavigate} />
|
<${BacklinksPanel} noteId=${note.id} onNavigate=${onNavigate} />
|
||||||
</div>
|
</div>
|
||||||
@@ -1050,6 +1114,10 @@
|
|||||||
var [showUnfiled, setShowUnfiled] = useState(false);
|
var [showUnfiled, setShowUnfiled] = useState(false);
|
||||||
var [allTags, setAllTags] = useState([]);
|
var [allTags, setAllTags] = useState([]);
|
||||||
var [activeTag, setActiveTag] = useState(null);
|
var [activeTag, setActiveTag] = useState(null);
|
||||||
|
var [sidebarTab, setSidebarTab] = useState('notes');
|
||||||
|
var [sidebarHeadings, setSidebarHeadings] = useState([]);
|
||||||
|
var [activeHeadingIdx, setActiveHeadingIdx] = useState(-1);
|
||||||
|
var scrollToHeadingRef = useRef(null);
|
||||||
|
|
||||||
// ── Load note list ────────────────────────
|
// ── Load note list ────────────────────────
|
||||||
var loadNotes = useCallback(async function() {
|
var loadNotes = useCallback(async function() {
|
||||||
@@ -1161,6 +1229,9 @@
|
|||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
setActiveId(null);
|
setActiveId(null);
|
||||||
setActiveNote(null);
|
setActiveNote(null);
|
||||||
|
setSidebarTab('notes');
|
||||||
|
setSidebarHeadings([]);
|
||||||
|
setActiveHeadingIdx(-1);
|
||||||
loadNotes();
|
loadNotes();
|
||||||
loadStats();
|
loadStats();
|
||||||
loadTags();
|
loadTags();
|
||||||
@@ -1317,38 +1388,42 @@
|
|||||||
<//>
|
<//>
|
||||||
<div class="notes-app">
|
<div class="notes-app">
|
||||||
<div class="notes-sidebar">
|
<div class="notes-sidebar">
|
||||||
<div class="notes-sidebar__header">
|
<${SidebarTabs} activeTab=${sidebarTab} onTabChange=${setSidebarTab} noteSelected=${!!activeNote} />
|
||||||
<span class="notes-sidebar__title">Notes</span>
|
${sidebarTab === 'notes' && html`
|
||||||
${stats && html`<span class="notes-sidebar__count">${stats.total}</span>`}
|
<${FolderTree} folders=${folders}
|
||||||
</div>
|
activeFolderId=${activeFolderId} showUnfiled=${showUnfiled}
|
||||||
<${FolderTree} folders=${folders}
|
onSelectFolder=${handleSelectFolder} onSelectAll=${handleSelectAll}
|
||||||
activeFolderId=${activeFolderId} showUnfiled=${showUnfiled}
|
onSelectUnfiled=${handleSelectUnfiled}
|
||||||
onSelectFolder=${handleSelectFolder} onSelectAll=${handleSelectAll}
|
onCreateFolder=${handleCreateFolder}
|
||||||
onSelectUnfiled=${handleSelectUnfiled}
|
onRenameFolder=${handleRenameFolder}
|
||||||
onCreateFolder=${handleCreateFolder}
|
onDeleteFolder=${handleDeleteFolder}
|
||||||
onRenameFolder=${handleRenameFolder}
|
onDropNote=${handleDropNote} />
|
||||||
onDeleteFolder=${handleDeleteFolder}
|
<${TagFilter} tags=${allTags} activeTag=${activeTag} onSelect=${handleTagFilter} />
|
||||||
onDropNote=${handleDropNote} />
|
<div class="notes-search">
|
||||||
<${TagFilter} tags=${allTags} activeTag=${activeTag} onSelect=${handleTagFilter} />
|
<input type="text" placeholder="Search notes…"
|
||||||
<div class="notes-search">
|
value=${searchTerm} onInput=${handleSearch} />
|
||||||
<input type="text" placeholder="Search notes…"
|
</div>
|
||||||
value=${searchTerm} onInput=${handleSearch} />
|
<div class="notes-list">
|
||||||
</div>
|
${loading && html`<div class="notes-list__loading"><${Spinner} size="md" /></div>`}
|
||||||
<div class="notes-list">
|
${!loading && notes.length === 0 && html`
|
||||||
${loading && html`<div class="notes-list__loading"><${Spinner} size="md" /></div>`}
|
<div class="notes-list__empty">
|
||||||
${!loading && notes.length === 0 && html`
|
${searchTerm ? 'No matching notes' : (activeTag ? 'No notes with tag "' + activeTag + '"' : 'No notes yet. Click + New Note to start.')}
|
||||||
<div class="notes-list__empty">
|
</div>
|
||||||
${searchTerm ? 'No matching notes' : (activeTag ? 'No notes with tag "' + activeTag + '"' : 'No notes yet. Click + New Note to start.')}
|
`}
|
||||||
</div>
|
${!loading && notes.map(function(n) {
|
||||||
`}
|
return html`
|
||||||
${!loading && notes.map(function(n) {
|
<${NoteCard} key=${n.id} note=${n}
|
||||||
return html`
|
active=${n.id === activeId}
|
||||||
<${NoteCard} key=${n.id} note=${n}
|
onClick=${() => handleSelect(n.id)} />
|
||||||
active=${n.id === activeId}
|
`;
|
||||||
onClick=${() => handleSelect(n.id)} />
|
})}
|
||||||
`;
|
</div>
|
||||||
})}
|
`}
|
||||||
</div>
|
${sidebarTab === 'outline' && html`
|
||||||
|
<${SidebarOutline} headings=${sidebarHeadings}
|
||||||
|
activeHeadingIdx=${activeHeadingIdx}
|
||||||
|
onScrollToHeading=${function(h) { if (scrollToHeadingRef.current) scrollToHeadingRef.current(h); }} />
|
||||||
|
`}
|
||||||
</div>
|
</div>
|
||||||
<${EditorPane} note=${activeNote} folders=${folders} allTags=${allTags}
|
<${EditorPane} note=${activeNote} folders=${folders} allTags=${allTags}
|
||||||
onSave=${handleRefresh}
|
onSave=${handleRefresh}
|
||||||
@@ -1356,7 +1431,10 @@
|
|||||||
onRefresh=${handleRefresh}
|
onRefresh=${handleRefresh}
|
||||||
onTagsChanged=${handleTagsChanged}
|
onTagsChanged=${handleTagsChanged}
|
||||||
onNavigate=${handleSelect}
|
onNavigate=${handleSelect}
|
||||||
onNavigateToTitle=${handleNavigateToTitle} />
|
onNavigateToTitle=${handleNavigateToTitle}
|
||||||
|
onHeadingsChange=${setSidebarHeadings}
|
||||||
|
scrollToHeadingRef=${scrollToHeadingRef}
|
||||||
|
onActiveHeadingChange=${setActiveHeadingIdx} />
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
"route": "/s/notes",
|
"route": "/s/notes",
|
||||||
"auth": "authenticated",
|
"auth": "authenticated",
|
||||||
"layout": "single",
|
"layout": "single",
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"icon": "📝",
|
"icon": "📝",
|
||||||
"description": "Markdown notes surface with rich editor, folders, tags, backlinks, import/export, and document outline.",
|
"description": "Markdown notes surface with rich editor, folders, tags, backlinks, import/export, sidebar tabs, and document outline.",
|
||||||
"author": "switchboard",
|
"author": "switchboard",
|
||||||
|
|
||||||
"permissions": ["db.write"],
|
"permissions": ["db.write"],
|
||||||
|
|||||||
Reference in New Issue
Block a user