Skip to content

Commit eb9476b

Browse files
committed
feat(features): home-End processings for edit mode & Tab-trigger + access to page name on Search
1 parent b006d3f commit eb9476b

File tree

3 files changed

+162
-51
lines changed

3 files changed

+162
-51
lines changed

src/app.ts

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -19,67 +19,46 @@ import {
1919

2020
splitByLines, splitBySentences, splitByWords,
2121
} from './commands'
22+
import { improveCursorMovement_KeyDownListener, improveSearch_KeyDownListener } from './features'
2223
import { getChosenBlocks, p, scrollToBlock } from './utils'
2324

2425

2526
const DEV = process.env.NODE_ENV === 'development'
2627

2728
const settingsSchema: SettingSchemaDesc[] = [
2829
{
29-
key: 'splittingHeading',
30-
title: '⛓️ Split & Join',
31-
description: '',
32-
type: 'heading',
33-
default: null
30+
key: 'enableHomeEnd',
31+
title: 'Enable improved «Home» / «End» keys processing?',
32+
description: `
33+
Double-press the <code>Home</code> / <code>End</code> key (in edit mode) to go to the block start / end. <br/>
34+
MacOS's <code>⌘ ←</code> / <code>⌘ →</code> and Windows's <code>fn ←</code> / <code>fn →</code> are also supported. <br/>
35+
<p><img width="200px" src="https://github.com/stdword/logseq13-missing-commands/assets/1984175/4773523a-5900-4b48-b196-f6cb39799548"/></p>
36+
<i>Restriction</i>: This feature only works for natural lines of block, which have a «new line» character («\\n»). It does not work with lines created due to the size of the layout. In such cases, the only way to proceed is to press <code>Esc</code> to exit edit mode and then use the <code>←</code> or <code>→</code> arrow key to re-enter it. <br/>
37+
`.trim(),
38+
type: 'enum',
39+
enumPicker: 'radio',
40+
enumChoices: ['Yes', 'No'],
41+
default: 'Yes',
3442
},
3543
{
36-
key: 'storeChildBlocksIn',
37-
title: 'Where to store nested blocks when splitting the block?',
44+
key: 'enableSearchImprovements',
45+
title: 'Enable improved keys processing on Search?',
3846
description: `
39-
<span style="display: flex; gap: 1rem; align-items: center;">
40-
<div>
41-
<ul>
42-
<li>First Last</li>
43-
<ul>
44-
<li>nested</li>
45-
</ul>
46-
</ul>
47-
<p style="margin: 0px"> </p>
48-
</div>
49-
<div>→</div>
50-
<div>
51-
<ul>
52-
<li>First</li>
53-
<ul>
54-
<li>nested</li>
55-
</ul>
56-
<li>Last</li>
57-
</ul>
58-
</div>
59-
<div>OR</div>
60-
<div>
61-
<ul>
62-
<li>First</li>
63-
<li>Last</li>
64-
<ul>
65-
<li>nested</li>
66-
</ul>
67-
</ul>
68-
</div>
69-
</span>
47+
1) Press <code>Tab</code> to fill the input with selected search item.<br/>
48+
<p><img width="600px" src="https://github.com/stdword/logseq13-missing-commands/assets/1984175/bf27f3a6-8464-4e1f-b967-e5e9efe46e21"/></p>
49+
`.trim() + '\n' + `
50+
2) Press <code>←</code> arrow (with empty input) to fill the input with the current page name.<br/>
51+
<p><img width="600px" src="https://github.com/stdword/logseq13-missing-commands/assets/1984175/a083c0c1-604a-4514-8732-41b6a8c7b1ba"/></p>
7052
`.trim(),
7153
type: 'enum',
7254
enumPicker: 'radio',
73-
enumChoices: ['In the First block', 'In the Last block'],
74-
default: 'In the Last block',
55+
enumChoices: ['Yes', 'No'],
56+
default: 'Yes',
7557
},
7658
]
7759
const settings_: any = settingsSchema.reduce((r, v) => ({ ...r, [v.key]: v}), {})
7860

7961

80-
async function onAppSettingsChanged() {
81-
}
82-
8362
async function init() {
8463
if (DEV) {
8564
logseq.UI.showMsg(
@@ -89,29 +68,50 @@ async function init() {
8968
)
9069
}
9170

92-
// logseq.useSettingsSchema(settingsSchema)
71+
logseq.useSettingsSchema(settingsSchema)
9372

9473
console.info(p`Loaded`)
9574
}
75+
async function postInit(settings) {
76+
await onAppSettingsChanged(settings, undefined)
77+
}
78+
async function onAppSettingsChanged(current, old) {
79+
if (!old || current.enableHomeEnd !== old.enableHomeEnd) {
80+
parent.document.removeEventListener('keydown', improveCursorMovement_KeyDownListener)
81+
if (current.enableHomeEnd === 'Yes')
82+
parent.document.addEventListener('keydown', improveCursorMovement_KeyDownListener)
83+
}
84+
85+
if (!old || current.enableSearchImprovements !== old.enableSearchImprovements) {
86+
parent.document.removeEventListener('keydown', improveSearch_KeyDownListener, true)
87+
if (current.enableSearchImprovements === 'Yes')
88+
parent.document.addEventListener('keydown', improveSearch_KeyDownListener, true)
89+
}
90+
}
9691

97-
async function postInit() {
98-
await onAppSettingsChanged()
92+
const _emptyStyle = '/**/'
93+
function provideStyle(key: string, style: string = _emptyStyle) {
94+
logseq.provideStyle({key, style})
95+
return () => {logseq.provideStyle({key, style: _emptyStyle})}
9996
}
10097

98+
10199
async function main() {
102100
await init()
103101

104102
const settings = logseq.settings!
103+
const settingsOff = logseq.onSettingsChanged(onAppSettingsChanged)
105104

106-
logseq.onSettingsChanged(async (old, new_) => {
107-
await onAppSettingsChanged()
105+
logseq.beforeunload(async () => {
106+
settingsOff()
108107
})
109108

109+
110110
function setting_storeChildBlocksIn() {
111-
return false
112-
// return settings.storeChildBlocksIn === settings_.storeChildBlocksIn.enumChoices[0]
111+
return false // the last one
113112
}
114113

114+
115115
// Decoration
116116
logseq.App.registerCommandPalette({
117117
label: ICON + ' Toggle auto heading', key: 'mc-1-auto-heading',
@@ -300,7 +300,7 @@ async function main() {
300300
}, (e) => shuffleBlocksCommand() )
301301

302302

303-
await postInit()
303+
await postInit(settings)
304304
}
305305

306306

src/features.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { setNativeValue } from './utils'
2+
3+
4+
/**
5+
* Sublime Text-like double ⌘→ or ⌘← to move cursor to the end / start of the text area
6+
*/
7+
export function improveCursorMovement_KeyDownListener(e: KeyboardEvent) {
8+
if (!e.target)
9+
return
10+
11+
const target = e.target as HTMLInputElement
12+
if (target.tagName !== 'TEXTAREA')
13+
return
14+
15+
if (!['ArrowRight', 'ArrowLeft', 'Home', 'End'].includes(e.key))
16+
return
17+
18+
19+
const homeEvent = (
20+
(e.key === 'ArrowLeft' && e.metaKey && !e.altKey && !e.ctrlKey) ||
21+
(e.key === 'Home')
22+
)
23+
const endEvent = (
24+
(e.key === 'ArrowRight' && e.metaKey && !e.altKey && !e.ctrlKey) ||
25+
(e.key === 'End')
26+
)
27+
28+
if (target.selectionEnd === 0 || target.selectionStart === target.value.length)
29+
return
30+
31+
if (endEvent && target.value[target.selectionEnd as number] === '\n') {
32+
if (e.shiftKey)
33+
target.selectionEnd = -1
34+
else
35+
target.selectionStart = -1
36+
} else if (homeEvent && target.value[(target.selectionStart as number) - 1] === '\n')
37+
target.selectionStart = 0
38+
39+
// @ts-expect-error
40+
target.scrollIntoViewIfNeeded()
41+
}
42+
43+
44+
/**
45+
* TAB-trigger and access current page name on Search
46+
*/
47+
export async function improveSearch_KeyDownListener(e: KeyboardEvent) {
48+
if (!e.target)
49+
return
50+
51+
const target = e.target as HTMLInputElement
52+
if (target.tagName !== 'INPUT' || target.id !== 'search')
53+
return
54+
55+
if (!['ArrowLeft', 'Tab'].includes(e.key))
56+
return
57+
58+
if ( !(
59+
!e.altKey && !e.shiftKey && !e.metaKey && !e.ctrlKey
60+
))
61+
return
62+
63+
if (e.key === 'ArrowLeft') {
64+
if (!target.value) {
65+
e.preventDefault()
66+
const page = await logseq.Editor.getCurrentPage()
67+
if (page)
68+
setNativeValue(target, page.originalName, true)
69+
}
70+
return
71+
}
72+
73+
e.preventDefault()
74+
75+
const panel = target.closest('.cp__cmdk')!
76+
const active = panel.querySelector('.\\!opacity-100')
77+
if (!active)
78+
return
79+
80+
// skip blocks results
81+
if (!active.parentElement!.classList.contains('search-results'))
82+
return
83+
84+
const label = active!.querySelector('.text-sm.font-medium')!.childNodes[0] as HTMLElement
85+
86+
let text
87+
if (label.tagName === 'DIV') { // page with alias
88+
const walker = document.createTreeWalker(label, NodeFilter.SHOW_TEXT, null)!
89+
text = walker.nextNode()!.textContent
90+
} else if (label.tagName === 'SPAN') { // normal page, command, create command
91+
text = label.textContent
92+
}
93+
94+
if (e.key === 'Tab')
95+
if (target.value.toLowerCase() !== text.toLowerCase())
96+
setNativeValue(target, text, true)
97+
}

src/utils/logseq.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,17 @@ export async function insertBatchBlockBefore(
492492

493493
return inserted
494494
}
495+
496+
export function setNativeValue(element, value, needToDispatch) {
497+
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value')!.set!
498+
const prototype = Object.getPrototypeOf(element)
499+
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')!.set!
500+
501+
if (valueSetter && valueSetter !== prototypeValueSetter)
502+
prototypeValueSetter.call(element, value)
503+
else
504+
valueSetter.call(element, value)
505+
506+
if (needToDispatch)
507+
element.dispatchEvent(new Event('input', {bubbles: true}))
508+
}

0 commit comments

Comments
 (0)