Skip to content

Commit f733f5b

Browse files
Description edit
1 parent 462ef6c commit f733f5b

File tree

6 files changed

+208
-2
lines changed

6 files changed

+208
-2
lines changed

src/App.svelte

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,58 @@
340340
tick().then(() => focusItem(focusedItemId))
341341
}
342342
}
343+
344+
if (event.key === 'Tab' && !event.target.closest('[contenteditable]') && !event.target.closest('.search-input')) {
345+
if ($selection.size > 0) {
346+
event.preventDefault()
347+
if (event.shiftKey) {
348+
itemStore.outdentSelected()
349+
} else {
350+
itemStore.indentSelected()
351+
}
352+
}
353+
}
354+
355+
if ((event.metaKey || event.ctrlKey) && event.altKey && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
356+
event.preventDefault()
357+
358+
const activeEditor = document.activeElement?.closest('[contenteditable]')
359+
360+
if ($selection.size > 0) {
361+
if (activeEditor) {
362+
activeEditor.blur()
363+
}
364+
if (event.key === 'ArrowUp') {
365+
itemStore.moveSelectedUp()
366+
} else {
367+
itemStore.moveSelectedDown()
368+
}
369+
} else if (activeEditor) {
370+
const itemElement = activeEditor.closest('.item') || activeEditor.closest('[id^="item_"]')
371+
if (itemElement) {
372+
const itemId = itemElement.id.replace('item_', '')
373+
if (itemId) {
374+
const selection = window.getSelection()
375+
const range = selection?.rangeCount > 0 ? selection.getRangeAt(0).cloneRange() : null
376+
377+
if (event.key === 'ArrowUp') {
378+
itemStore.moveItemUp(itemId)
379+
} else {
380+
itemStore.moveItemDown(itemId)
381+
}
382+
383+
tick().then(() => {
384+
activeEditor.focus()
385+
if (range) {
386+
const newSelection = window.getSelection()
387+
newSelection.removeAllRanges()
388+
newSelection.addRange(range)
389+
}
390+
})
391+
}
392+
}
393+
}
394+
}
343395
}
344396
345397
function getItemIdFromElement(element) {

src/components/List.svelte

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@
153153
}
154154
if (item.children?.length) {
155155
focusItem(item.children[0].id)
156+
} else {
157+
const newId = generateId()
158+
itemStore.updateItem(item.id, {
159+
children: [
160+
{ id: newId, text: '', description: '', completed: false, open: true, children: [] }
161+
]
162+
})
163+
tick().then(() => focusItem(newId))
156164
}
157165
}
158166
@@ -162,6 +170,22 @@
162170
}
163171
}
164172
173+
function handleTitleDescription() {
174+
showDescriptionEditor = true
175+
tick().then(() => {
176+
requestAnimationFrame(() => {
177+
if (containerElement) {
178+
const el = containerElement.querySelector('.zoomed_description [contenteditable]')
179+
if (el) {
180+
el.focus()
181+
return
182+
}
183+
}
184+
focusDescription(item.id)
185+
})
186+
})
187+
}
188+
165189
function handleTitleNewBullet() {
166190
if (!item.children?.length) {
167191
const newId = generateId()
@@ -251,6 +275,7 @@
251275
itemId={item.id}
252276
on:selectdown={handleTitleSelectDown}
253277
on:newbullet={handleTitleNewBullet}
278+
on:description={handleTitleDescription}
254279
on:change={() => itemStore.updateItem(item.id, { text: item.text })}
255280
on:hashtagclick={handleHashtagClick}
256281
on:itemrefclick={handleItemRefClick}
@@ -262,6 +287,7 @@
262287
<RichEditor
263288
bind:value={item.description}
264289
isDescription={true}
290+
isZoomedRoot={true}
265291
showPlaceholder={false}
266292
{highlightPhrase}
267293
editorClass="editable description"

src/components/RichEditor.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
export let value = ''
2222
export let placeholder = ''
2323
export let isDescription = false
24+
export let isZoomedRoot = false
2425
export let highlightPhrase = null
2526
export let editorClass = 'editable'
2627
export let showPlaceholder = true
@@ -124,6 +125,7 @@
124125
}),
125126
KeyboardExtension.configure({
126127
isDescription,
128+
isZoomedRootDescription: isZoomedRoot && isDescription,
127129
onEnter: () => {
128130
if (isDescription) {
129131
dispatch('exitdescription')

src/components/SearchBar.svelte

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,25 @@
1919
function handleKeyDown(event) {
2020
if (event.key === 'Escape') {
2121
handleClear()
22+
return
2223
}
24+
25+
if (event.metaKey || event.ctrlKey || event.altKey) {
26+
event.stopPropagation()
27+
return
28+
}
29+
30+
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Enter', 'Tab', 'Delete', 'Backspace'].includes(event.key)) {
31+
event.stopPropagation()
32+
}
33+
}
34+
35+
function handleKeyUp(event) {
36+
event.stopPropagation()
37+
}
38+
39+
function handleKeyPress(event) {
40+
event.stopPropagation()
2341
}
2442
2543
function handleGlobalKeyDown(event) {
@@ -49,13 +67,15 @@
4967
value={searchValue}
5068
on:input={handleInput}
5169
on:keydown={handleKeyDown}
70+
on:keyup={handleKeyUp}
71+
on:keypress={handleKeyPress}
5272
type="text"
5373
placeholder="Search... (⌘F)"
5474
class="search-input"
5575
/>
5676
5777
{#if searchValue}
58-
<button class="clear-button" on:click={handleClear} type="button">
78+
<button class="clear-button" on:click={handleClear} type="button" aria-label="Clear search">
5979
<svg width="14" height="14" viewBox="0 0 256 256">
6080
<path
6181
fill="currentColor"

src/extensions/KeyboardExtension.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ export const KeyboardExtension = Extension.create({
1616
onArrowDown: null,
1717
onShiftArrowUp: null,
1818
onShiftArrowDown: null,
19-
isDescription: false
19+
isDescription: false,
20+
isZoomedRootDescription: false
2021
}
2122
},
2223

2324
addKeyboardShortcuts() {
2425
return {
2526
'Enter': ({ editor }) => {
27+
if (this.options.isZoomedRootDescription) {
28+
return false
29+
}
2630
if (this.options.onEnter) {
2731
this.options.onEnter()
2832
return true
@@ -31,6 +35,10 @@ export const KeyboardExtension = Extension.create({
3135
},
3236

3337
'Shift-Enter': ({ editor }) => {
38+
if (this.options.isZoomedRootDescription && this.options.onEnter) {
39+
this.options.onEnter()
40+
return true
41+
}
3442
if (this.options.onShiftEnter) {
3543
this.options.onShiftEnter()
3644
return true

src/stores/itemStore.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,98 @@ function createItemStore() {
325325
})
326326
}
327327

328+
function indentSelected() {
329+
const sel = get(selection)
330+
if (sel.size === 0) return
331+
332+
const rootItems = get(items)
333+
const zoomId = get(zoomedItemId)
334+
const root = zoomId ? findItem(rootItems, zoomId) || rootItems : rootItems
335+
const flat = flattenVisibleTree(root)
336+
337+
const idsInOrder = flat.filter(item => sel.has(item.id)).map(item => item.id)
338+
339+
for (const id of idsInOrder) {
340+
indentItem(id)
341+
}
342+
}
343+
344+
function outdentSelected() {
345+
const sel = get(selection)
346+
if (sel.size === 0) return
347+
348+
const rootItems = get(items)
349+
const zoomId = get(zoomedItemId)
350+
const root = zoomId ? findItem(rootItems, zoomId) || rootItems : rootItems
351+
const flat = flattenVisibleTree(root)
352+
353+
const idsInOrder = flat.filter(item => sel.has(item.id)).map(item => item.id)
354+
355+
for (const id of [...idsInOrder].reverse()) {
356+
outdentItem(id)
357+
}
358+
}
359+
360+
function moveItemUp(id) {
361+
updateItems(root => {
362+
const parent = findParent(root, id)
363+
if (!parent?.children) return
364+
365+
const idx = findItemIndex(parent, id)
366+
if (idx <= 0) return
367+
368+
const temp = parent.children[idx]
369+
parent.children[idx] = parent.children[idx - 1]
370+
parent.children[idx - 1] = temp
371+
})
372+
}
373+
374+
function moveItemDown(id) {
375+
updateItems(root => {
376+
const parent = findParent(root, id)
377+
if (!parent?.children) return
378+
379+
const idx = findItemIndex(parent, id)
380+
if (idx < 0 || idx >= parent.children.length - 1) return
381+
382+
const temp = parent.children[idx]
383+
parent.children[idx] = parent.children[idx + 1]
384+
parent.children[idx + 1] = temp
385+
})
386+
}
387+
388+
function moveSelectedUp() {
389+
const sel = get(selection)
390+
if (sel.size === 0) return
391+
392+
const rootItems = get(items)
393+
const zoomId = get(zoomedItemId)
394+
const root = zoomId ? findItem(rootItems, zoomId) || rootItems : rootItems
395+
const flat = flattenVisibleTree(root)
396+
397+
const idsInOrder = flat.filter(item => sel.has(item.id)).map(item => item.id)
398+
399+
for (const id of idsInOrder) {
400+
moveItemUp(id)
401+
}
402+
}
403+
404+
function moveSelectedDown() {
405+
const sel = get(selection)
406+
if (sel.size === 0) return
407+
408+
const rootItems = get(items)
409+
const zoomId = get(zoomedItemId)
410+
const root = zoomId ? findItem(rootItems, zoomId) || rootItems : rootItems
411+
const flat = flattenVisibleTree(root)
412+
413+
const idsInOrder = flat.filter(item => sel.has(item.id)).map(item => item.id)
414+
415+
for (const id of [...idsInOrder].reverse()) {
416+
moveItemDown(id)
417+
}
418+
}
419+
328420
function setSearch(query) {
329421
searchQuery.set(query)
330422
}
@@ -594,6 +686,12 @@ function createItemStore() {
594686
toggleOpen,
595687
indentItem,
596688
outdentItem,
689+
indentSelected,
690+
outdentSelected,
691+
moveItemUp,
692+
moveItemDown,
693+
moveSelectedUp,
694+
moveSelectedDown,
597695
setSearch,
598696
clearSearch,
599697
select,

0 commit comments

Comments
 (0)