Skip to content

Commit 89ebb29

Browse files
committed
feat(features): mouse click with opt/alt on page ref
1 parent f8282c6 commit 89ebb29

File tree

3 files changed

+102
-7
lines changed

3 files changed

+102
-7
lines changed

src/app.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
lowerCase, upperCase, titleCaseWords, titleCaseSentences,
2323
} from './commands'
2424
import { MARKUP, magicQuotes, magicWrap } from './commands/magic_markup'
25-
import { improveCursorMovementFeature, improveSearchFeature, spareBlocksFeature } from './features'
25+
import { improveCursorMovementFeature, improveMouseRefClick, improveSearchFeature, spareBlocksFeature } from './features'
2626
import { borderView, columnsView, galleryView, hideDotRefs, tabularView } from './views'
2727
import { getChosenBlocks, p, scrollToBlock } from './utils'
2828

@@ -97,6 +97,17 @@ const settingsSchema: SettingSchemaDesc[] = [
9797
enumChoices: ['Yes', 'No'],
9898
default: 'Yes',
9999
},
100+
{
101+
key: 'enableMouseRefClick',
102+
title: 'Enable block editing on mouse click to [[reference]]?',
103+
description: `
104+
With <code>⌥</code> (or <code>Alt</code> for Windows) pressed.
105+
`.trim(),
106+
type: 'enum',
107+
enumPicker: 'radio',
108+
enumChoices: ['Yes', 'No'],
109+
default: 'Yes',
110+
},
100111
{
101112
key: 'spareBlocksSpace',
102113
title: 'Spare space between 1-level blocks',
@@ -242,6 +253,12 @@ async function onAppSettingsChanged(current, old) {
242253
improveSearchFeature(true)
243254
}
244255

256+
if (!old || current.enableMouseRefClick !== old.enableMouseRefClick) {
257+
improveMouseRefClick(false)
258+
if (current.enableMouseRefClick === 'Yes')
259+
improveMouseRefClick(true)
260+
}
261+
245262
if (!old || current.spareBlocksSpace !== old.spareBlocksSpace)
246263
spareBlocksFeature(current.spareBlocksSpace)
247264

src/features.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { provideStyle, setNativeValue } from './utils'
1+
import { PropertiesUtils, escapeForRegExp, provideStyle, setNativeValue } from './utils'
22

33
import spareBlocksStyle from './css/spare_blocks.css?inline'
44

@@ -109,6 +109,88 @@ export function improveSearchFeature(toggle: boolean) {
109109
parent.document.removeEventListener('keydown', improveSearch_KeyDownListener, true)
110110
}
111111

112+
/**
113+
* Edit block on mouse click to reference with ALT/OPT pressed
114+
*/
115+
async function improveMouseRefClick_MouseUpListener(e: MouseEvent) {
116+
if (!e.target)
117+
return
118+
119+
if (! (e.altKey && !e.shiftKey && !e.metaKey && !e.ctrlKey))
120+
return
121+
122+
const target = e.target as HTMLElement
123+
if (target.tagName !== 'A')
124+
return
125+
126+
const isTag = target.classList.contains('tag')
127+
if (!target.classList.contains('page-ref') && !isTag)
128+
return
129+
130+
const block = target.closest('.block-content') as HTMLElement
131+
// @ts-expect-error
132+
const uuid = block.attributes.blockid.value
133+
if (!uuid)
134+
return
135+
136+
e.stopPropagation()
137+
138+
let content = (await logseq.Editor.getBlock(uuid))!.content
139+
content = PropertiesUtils.deletePropertyFromString(content, 'id')
140+
141+
// get ref text
142+
const walker = document.createTreeWalker(target, NodeFilter.SHOW_TEXT, null)!
143+
let text = ''
144+
let node: Node | null
145+
while (true) {
146+
node = walker.nextNode()
147+
if (!node)
148+
break
149+
text = node.textContent!
150+
}
151+
152+
if (isTag)
153+
text = text.slice(1)
154+
155+
// find ref position in block
156+
const escapedText = escapeForRegExp(text)
157+
const refText = `\\[\\[${escapedText}\\]\\]`
158+
159+
let startRefPos = -1
160+
if (isTag) {
161+
startRefPos = content.search(new RegExp(`#${escapedText}`, 'u'))
162+
if (startRefPos !== -1)
163+
startRefPos += 1 // for #
164+
165+
if (startRefPos === -1) {
166+
startRefPos = content.search(new RegExp(`#${refText}`, 'u'))
167+
if (startRefPos !== -1)
168+
startRefPos += 3 // for #[[
169+
}
170+
} else {
171+
startRefPos = content.search(new RegExp(refText, 'u'))
172+
if (startRefPos !== -1)
173+
startRefPos += 2 // for [[
174+
}
175+
176+
if (startRefPos === -1)
177+
startRefPos = 0
178+
179+
// find relative click position
180+
const textSize = (target.textContent ?? '').length || 1
181+
const rect = target.getBoundingClientRect()
182+
const relativePos = e.x - rect.left
183+
const charPos = Math.round(textSize * relativePos / rect.width)
184+
185+
await logseq.Editor.editBlock(uuid, {pos: startRefPos + charPos})
186+
}
187+
export function improveMouseRefClick(toggle: boolean) {
188+
if (toggle)
189+
parent.document.addEventListener('mouseup', improveMouseRefClick_MouseUpListener, true)
190+
else
191+
parent.document.removeEventListener('mouseup', improveMouseRefClick_MouseUpListener, true)
192+
}
193+
112194
/**
113195
* CSS: Make spare space between 1-level blocks
114196
*/

src/utils/other.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,13 @@ export function reduceTextWithLength(text: string, length: number, suffix = '...
7676

7777
export function escapeForRegExp(str: string) {
7878
const specials = [
79-
// '-', '^', '$',
79+
'-', '^', '$',
8080
'/', '.', '*', '+', '?', '|',
8181
'(', ')', '[', ']', '{', '}', '\\',
8282
]
8383

8484
const replacer = new RegExp('(\\' + specials.join('|\\') + ')', 'g')
8585
return str.replaceAll(replacer, '\\$1')
86-
87-
// alternative from MDN
88-
// return str.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&')
89-
// $& means the whole matched string
9086
}
9187

9288

0 commit comments

Comments
 (0)