Skip to content

Commit 24e0935

Browse files
feat: add snippet link via deep link (#564)
* feat: add snippet link via deep link * chore: clean
1 parent e58744e commit 24e0935

9 files changed

Lines changed: 171 additions & 82 deletions

File tree

src/main/index.ts

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,22 @@ import { store } from './store'
1111
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true' // Отключаем security warnings
1212

1313
const isDev = process.env.NODE_ENV === 'development'
14+
const gotTheLock = app.requestSingleInstanceLock()
1415

1516
let mainWindow: BrowserWindow
1617
let isQuitting = false
1718

19+
if (process.defaultApp) {
20+
if (process.argv.length >= 2) {
21+
app.setAsDefaultProtocolClient('masscode', process.execPath, [
22+
path.resolve(process.argv[1]),
23+
])
24+
}
25+
}
26+
else {
27+
app.setAsDefaultProtocolClient('masscode')
28+
}
29+
1830
function createWindow() {
1931
const bounds = store.app.get('bounds')
2032
mainWindow = new BrowserWindow({
@@ -54,37 +66,60 @@ function createWindow() {
5466
})
5567
}
5668

57-
app.whenReady().then(() => {
58-
createWindow()
59-
registerIPC()
69+
if (!gotTheLock) {
70+
app.quit()
71+
}
72+
else {
73+
app.whenReady().then(() => {
74+
createWindow()
75+
registerIPC()
6076

61-
initApi()
77+
initApi()
6278

63-
if (store.app.get('isAutoMigratedFromJson')) {
64-
return
65-
}
79+
if (store.app.get('isAutoMigratedFromJson')) {
80+
return
81+
}
6682

67-
try {
68-
const jsonDbPath = `${store.preferences.get('storagePath')}/db.json`
69-
const jsonData = readFileSync(jsonDbPath, 'utf8')
83+
try {
84+
const jsonDbPath = `${store.preferences.get('storagePath')}/db.json`
85+
const jsonData = readFileSync(jsonDbPath, 'utf8')
7086

71-
migrateJsonToSqlite(JSON.parse(jsonData))
72-
store.app.set('isAutoMigratedFromJson', true)
73-
}
74-
catch (err) {
75-
console.error('Error on auto migration JSON to SQLite:', err)
76-
}
77-
})
87+
migrateJsonToSqlite(JSON.parse(jsonData))
88+
store.app.set('isAutoMigratedFromJson', true)
89+
}
90+
catch (err) {
91+
console.error('Error on auto migration JSON to SQLite:', err)
92+
}
93+
})
7894

79-
app.on('activate', () => {
80-
mainWindow.show()
81-
})
95+
app.on('activate', () => {
96+
mainWindow.show()
97+
})
98+
99+
app.on('before-quit', () => {
100+
isQuitting = true
101+
})
82102

83-
app.on('before-quit', () => {
84-
isQuitting = true
85-
})
103+
app.on('window-all-closed', () => {
104+
if (process.platform !== 'darwin')
105+
app.quit()
106+
})
107+
108+
app.on('second-instance', (_, argv) => {
109+
if (mainWindow) {
110+
mainWindow.isMinimized() ? mainWindow.restore() : mainWindow.focus()
111+
}
86112

87-
app.on('window-all-closed', () => {
88-
if (process.platform !== 'darwin')
89-
app.quit()
90-
})
113+
if (process.platform !== 'darwin') {
114+
const url = argv.find(i => i.startsWith('masscode://'))
115+
BrowserWindow.getFocusedWindow()?.webContents.send(
116+
'system:deep-link',
117+
url,
118+
)
119+
}
120+
})
121+
122+
app.on('open-url', (_, url) => {
123+
BrowserWindow.getFocusedWindow()?.webContents.send('system:deep-link', url)
124+
})
125+
}

src/main/types/ipc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type MainMenuAction =
2222
| 'presentation-mode'
2323

2424
type DBAction = 'relaod' | 'move' | 'migrate' | 'clear'
25-
type SystemAction = 'reload' | 'open-external'
25+
type SystemAction = 'reload' | 'open-external' | 'deep-link'
2626
type PrettierAction = 'format'
2727
type FsAction = 'assets'
2828

src/renderer/components/editor/code-image/CodeImage.vue

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,6 @@ onMounted(() => {
217217
{{ i18n.t("button.background") }}
218218
</span>
219219
</div>
220-
<!-- <div class="flex items-center gap-2">
221-
<div class="w-4 h-4 rounded-sm gradient-disco border border-text" />
222-
<div class="w-4 h-4 rounded-sm gradient-salad" />
223-
</div> -->
224220
<EditorCodeImageBackgroundSwitch v-model:active="activeBackground" />
225221
</div>
226222
<div>

src/renderer/components/editor/markdown/Markdown.vue

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ marked.use({
4545
return `<div id="${id}" class="code-block"></div>`
4646
},
4747
link({ href, text }) {
48-
if (/^masscode:\/\/snippets/.test(href)) {
49-
const id = href.split('/').pop()
50-
return `<a href="${href}" class="snippet-link" data-snippet-id="${id}">${text}</a>`
48+
if (href.startsWith('masscode://')) {
49+
return `<a href="${href}" class="deep-link">${text}</a>`
5150
}
5251
else {
5352
return `<a href="${href}" class="external">${text}</a>`
@@ -134,19 +133,10 @@ function onLinkClick(event: MouseEvent) {
134133
135134
if (target.tagName === 'A') {
136135
const href = target.getAttribute('href')
137-
if (href) {
138-
if (target.classList.contains('snippet-link')) {
139-
const snippetId = target.getAttribute('data-snippet-id')
140-
if (snippetId) {
141-
// TODO: Implement snippet link
142-
143-
event.preventDefault()
144-
}
145-
}
146-
else if (target.classList.contains('external')) {
147-
ipc.invoke('system:open-external', href)
148-
event.preventDefault()
149-
}
136+
137+
if (href && target.classList.contains('external')) {
138+
ipc.invoke('system:open-external', href)
139+
event.preventDefault()
150140
}
151141
}
152142
}
@@ -176,33 +166,38 @@ watch(isDark, (value) => {
176166
</script>
177167

178168
<template>
179-
<EditorHeaderTool v-if="isShowHeaderTool">
180-
<div class="flex w-full justify-end gap-2 px-2">
181-
<UiActionButton
182-
:tooltip="i18n.t('button.zoomOut')"
183-
@click="onZoom('out')"
184-
>
185-
<Minus class="h-3 w-3" />
186-
</UiActionButton>
187-
<div class="tabular-nums select-none">
188-
{{ scaleToShow }}
169+
<div
170+
data-editor-markdown
171+
class="grid grid-rows-[auto_1fr] overflow-scroll"
172+
>
173+
<EditorHeaderTool v-if="isShowHeaderTool">
174+
<div class="flex w-full justify-end gap-2 px-2">
175+
<UiActionButton
176+
:tooltip="i18n.t('button.zoomOut')"
177+
@click="onZoom('out')"
178+
>
179+
<Minus class="h-3 w-3" />
180+
</UiActionButton>
181+
<div class="tabular-nums select-none">
182+
{{ scaleToShow }}
183+
</div>
184+
<UiActionButton
185+
:tooltip="i18n.t('button.zoomIn')"
186+
@click="onZoom('in')"
187+
>
188+
<Plus class="h-3 w-3" />
189+
</UiActionButton>
189190
</div>
190-
<UiActionButton
191-
:tooltip="i18n.t('button.zoomIn')"
192-
@click="onZoom('in')"
191+
</EditorHeaderTool>
192+
<PerfectScrollbar :options="{ minScrollbarLength: 20 }">
193+
<div
194+
ref="markdownRef"
195+
class="markdown-content"
193196
>
194-
<Plus class="h-3 w-3" />
195-
</UiActionButton>
196-
</div>
197-
</EditorHeaderTool>
198-
<PerfectScrollbar :options="{ minScrollbarLength: 20 }">
199-
<div
200-
ref="markdownRef"
201-
class="markdown-content"
202-
>
203-
<div v-html="renderedContent" />
204-
</div>
205-
</PerfectScrollbar>
197+
<div v-html="renderedContent" />
198+
</div>
199+
</PerfectScrollbar>
200+
</div>
206201
</template>
207202

208203
<style>

src/renderer/components/sidebar/folders/TreeNode.vue

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@ const {
3131
contextMenu,
3232
} = inject(treeKeys)!
3333
34-
const { highlightedFolderId, highlightedSnippetIds, highlightedTagId, state }
35-
= useApp()
34+
const {
35+
highlightedFolderId,
36+
highlightedSnippetIds,
37+
highlightedTagId,
38+
state,
39+
focusedFolderId,
40+
} = useApp()
3641
const { displayedSnippets, updateSnippets, selectFirstSnippet } = useSnippets()
3742
const { updateFolder, renameFolderId } = useFolders()
3843
3944
const hoveredId = ref()
4045
const overPosition = ref<Position>()
4146
const isDragged = ref(false)
42-
const isFocused = ref(false)
4347
4448
const rowRef = ref<HTMLElement>()
4549
@@ -56,9 +60,11 @@ const isHovered = computed(() => {
5660
})
5761
5862
const isSelected = computed(() => state.folderId === props.node.id)
63+
5964
const isHighlighted = computed(
6065
() => highlightedFolderId.value === props.node.id,
6166
)
67+
const isFocused = computed(() => focusedFolderId.value === props.node.id)
6268
6369
const isShowBetweenLine = computed(() => {
6470
if (!isAllowed.value)
@@ -99,7 +105,7 @@ function onClickNode(id: string | number) {
99105
highlightedFolderId.value = undefined
100106
highlightedTagId.value = undefined
101107
state.tagId = undefined
102-
isFocused.value = true
108+
focusedFolderId.value = Number(id)
103109
clickNode(Number(id))
104110
}
105111
@@ -203,7 +209,7 @@ async function onDrop(e: DragEvent) {
203209
}
204210
205211
onClickOutside(rowRef, () => {
206-
isFocused.value = false
212+
focusedFolderId.value = undefined
207213
highlightedFolderId.value = undefined
208214
})
209215

src/renderer/components/snippet/Item.vue

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as ContextMenu from '@/components/ui/shadcn/context-menu'
44
import { useApp, useDialog, useSnippets } from '@/composables'
55
import { LibraryFilter } from '@/composables/types'
66
import { i18n } from '@/electron'
7-
import { onClickOutside } from '@vueuse/core'
7+
import { onClickOutside, useClipboard } from '@vueuse/core'
88
import { format } from 'date-fns'
99
1010
interface Props {
@@ -17,6 +17,7 @@ const {
1717
highlightedSnippetIds,
1818
highlightedFolderId,
1919
isFocusedSnippetName,
20+
focusedSnippetId,
2021
state,
2122
} = useApp()
2223
@@ -33,11 +34,12 @@ const {
3334
} = useSnippets()
3435
3536
const { confirm } = useDialog()
37+
const { copy } = useClipboard()
3638
37-
const isFocused = ref(false)
3839
const snippetRef = ref<HTMLDivElement>()
3940
4041
const isSelected = computed(() => state.snippetId === props.snippet.id)
42+
4143
const isInMultiSelection = computed(
4244
() =>
4345
selectedSnippetIds.value.length > 1
@@ -47,6 +49,8 @@ const isHighlighted = computed(() =>
4749
highlightedSnippetIds.value.has(props.snippet.id),
4850
)
4951
52+
const isFocused = computed(() => focusedSnippetId.value === props.snippet.id)
53+
5054
const isDuplicateDisabled = computed(
5155
() => highlightedSnippetIds.value.size > 1,
5256
)
@@ -73,7 +77,7 @@ const folderName = computed(() => {
7377
7478
function onSnippetClick(id: number, event: MouseEvent) {
7579
selectSnippet(id, event.shiftKey)
76-
isFocused.value = true
80+
focusedSnippetId.value = id
7781
}
7882
7983
function onClickContextMenu() {
@@ -185,6 +189,13 @@ async function onDuplicate() {
185189
isFocusedSnippetName.value = true
186190
}
187191
192+
function onCopySnippetLink() {
193+
// copy(`masscode://folder/${state.folderId}/snippet/${props.snippet.id}`)
194+
copy(
195+
`masscode://goto?folderId=${state.folderId}&snippetId=${props.snippet.id}`,
196+
)
197+
}
198+
188199
function onDragStart(event: DragEvent) {
189200
const ids
190201
= selectedSnippetIds.value.length > 1
@@ -218,7 +229,7 @@ function onDragStart(event: DragEvent) {
218229
}
219230
220231
onClickOutside(snippetRef, () => {
221-
isFocused.value = false
232+
focusedSnippetId.value = undefined
222233
highlightedSnippetIds.value.clear()
223234
})
224235
</script>
@@ -267,6 +278,10 @@ onClickOutside(snippetRef, () => {
267278
}}
268279
</ContextMenu.Item>
269280
<ContextMenu.Separator />
281+
<ContextMenu.Item @click="onCopySnippetLink">
282+
{{ i18n.t("action.copy.snippetLink") }}
283+
</ContextMenu.Item>
284+
<ContextMenu.Separator />
270285
<ContextMenu.Item
271286
:disabled="isDuplicateDisabled"
272287
@click="onDuplicate"

src/renderer/composables/useApp.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const state = reactive<SavedState>(store.app.get('state') as SavedState)
1111
const highlightedFolderId = ref<number>()
1212
const highlightedSnippetIds = ref<Set<number>>(new Set())
1313
const highlightedTagId = ref<number>()
14+
const focusedFolderId = ref<number | undefined>()
15+
const focusedSnippetId = ref<number | undefined>()
1416

1517
const isFocusedSnippetName = ref(false)
1618
const isShowMarkdown = ref(false)
@@ -57,6 +59,8 @@ watch(state, () => {
5759

5860
export function useApp() {
5961
return {
62+
focusedFolderId,
63+
focusedSnippetId,
6064
highlightedFolderId,
6165
highlightedSnippetIds,
6266
highlightedTagId,

src/renderer/ipc/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { registerMainMenuListeners } from './listeners/main-menu'
2+
import { registerSystemListeners } from './listeners/system'
23

34
export function registerIPCListeners() {
45
registerMainMenuListeners()
6+
registerSystemListeners()
57
}

0 commit comments

Comments
 (0)