Skip to content
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"dependencies": {
"@iconscout/unicons": "^4.0.1",
"@masscode/json-server": "^0.18.0",
"@sipec/vue3-tags-input": "^3.0.4",
"@vueuse/core": "^8.1.2",
"ace-builds": "^1.4.14",
"electron-store": "^8.0.1",
Expand Down
35 changes: 32 additions & 3 deletions src/main/services/api/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ import { store } from '../../store'
import { nanoid } from 'nanoid'
import { API_PORT } from '../../config'
import path from 'path'
import type { DB, Snippet, Tag } from '@shared/types/main/db'
import { remove } from 'lodash'

export const createApiServer = () => {
const db = path.resolve(store.preferences.get('storagePath') + '/db.json')
const app = jsonServer.create()
const middlewares = jsonServer.defaults()
const router = jsonServer.router(db)
const router = jsonServer.router<DB>(db)

app.use(jsonServer.bodyParser)
app.use(middlewares)

app.post('/db/update/:table', (req, res) => {
const table = req.params.table
const table = req.params.table as keyof DB
const isAllowedTable = ['folders', 'snippets', 'tags'].includes(table)

if (!isAllowedTable) {
Expand All @@ -23,7 +25,7 @@ export const createApiServer = () => {
if (req.body.value?.length === 0) {
res.status(400).send("'value' is required")
} else {
const db: any = router.db.getState()
const db = router.db.getState()

db[table] = req.body.value

Expand All @@ -34,6 +36,33 @@ export const createApiServer = () => {
}
})

app.delete('/tags/:id', (req, res) => {
const id = req.params.id
const tags = router.db.get<Tag[]>('tags').value()
const snippets = router.db
.get<Snippet[]>('snippets')
.filter(i => i.tagsIds.includes(id))
.value()
const index = tags.findIndex(i => i.id === id)

snippets.forEach(i => {
remove(i.tagsIds, item => item === id)
})

tags.splice(index, 1)
router.db.write()

res.sendStatus(200)
})

app.get('/tags/:id/snippets', (req, res) => {
const id = req.params.id
const _snippets = router.db.get<Snippet[]>('snippets').value()
const snippets = _snippets.filter(i => i.tagsIds.includes(id))

res.status(200).send(snippets)
})

app.use((req, res, next) => {
if (req.method === 'POST') {
req.body.id = nanoid(8)
Expand Down
50 changes: 47 additions & 3 deletions src/main/services/ipc/context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,13 @@ export const subscribeToContextMenu = () => {
)

ipcMain.handle<ContextMenuPayload, ContextMenuResponse>(
'context-menu:folder',
'context-menu:library',
async (event, payload) => {
const { name, type, data } = payload

return new Promise(resolve => {
const menu = createPopupMenu([])

const createLanguageMenu = () => {
return languages.map(i => {
return {
Expand All @@ -198,7 +200,7 @@ export const subscribeToContextMenu = () => {
}) as MenuItemConstructorOptions[]
}

const menu = createPopupMenu([
const folderMenu: MenuItemConstructorOptions[] = [
{
label: 'New folder',
click: () => {
Expand Down Expand Up @@ -241,7 +243,49 @@ export const subscribeToContextMenu = () => {
label: 'Default Language',
submenu: createLanguageMenu()
}
])
]

const tagMenu: MenuItemConstructorOptions[] = [
{
label: 'Delete',
click: () => {
const buttonId = dialog.showMessageBoxSync({
message: `Are you sure you want to delete "${name}"?`,
detail:
'This will also cause all snippets to have that tag removed.',
buttons: ['Delete', 'Cancel'],
defaultId: 0,
cancelId: 1
})

if (buttonId === 0) {
resolve({
action: 'delete',
type,
data: undefined
})
} else {
resolve({
action: 'none',
type,
data: undefined
})
}
}
}
]

if (type === 'folder') {
folderMenu.forEach(i => {
menu.append(new MenuItem(i))
})
}

if (type === 'tag') {
tagMenu.forEach(i => {
menu.append(new MenuItem(i))
})
}

menu.on('menu-will-close', () => {
BrowserWindow.getFocusedWindow()?.webContents.send(
Expand Down
6 changes: 0 additions & 6 deletions src/renderer/assets/scss/variables.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
:root {
--sidebar-width: 180px;
--snippets-list-width: 250px;
--snippets-view-header-height: 60px;
--snippets-view-header-top-height: 30px;
--snippets-view-header-full-height: calc(var(--snippets-view-header-height) + var(--snippets-tags-height));
--snippets-view-footer-height: 30px;
--snippet-header-fragment-height: 25px;
--snippet-tab-height: 30px;
--title-bar-height: 15px;
--menu-header: 80px;

Expand Down
29 changes: 9 additions & 20 deletions src/renderer/components/editor/TheEditor.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
<template>
<div
class="editor"
:class="{ 'with-fragments': fragments }"
>
<div class="editor">
<div
ref="editorRef"
class="main"
Expand Down Expand Up @@ -36,7 +33,8 @@ import type { Ace } from 'ace-builds'
import ace from 'ace-builds'
import './module-resolver'
import type { Language } from '@shared/types/renderer/editor'
import { languages, oldLanguageMap } from './languages'
import { languages } from './languages'
import { useAppStore } from '@/store/app'

interface Props {
lang: Language
Expand All @@ -52,12 +50,13 @@ interface Emits {

const props = withDefaults(defineProps<Props>(), {
lang: 'typescript',
theme: 'chrome',
fragments: false
theme: 'chrome'
})

const emit = defineEmits<Emits>()

const appStore = useAppStore()

const editorRef = ref()
const cursorPosition = reactive({
row: 0,
Expand All @@ -70,6 +69,8 @@ const localLang = computed({
set: v => emit('update:lang', v)
})

const footerHeight = appStore.sizes.editor.footerHeight + 'px'

const init = async () => {
editor = ace.edit(editorRef.value, {
theme: `ace/theme/${props.theme}`,
Expand Down Expand Up @@ -141,20 +142,8 @@ watch(
<style lang="scss" scoped>
.editor {
padding-top: 4px;
&.with-fragments {
.main {
height: calc(
100vh - var(--title-bar-height) - var(--snippets-view-header-top-height) -
var(--snippets-view-footer-height) -
var(--snippet-header-fragment-height)
);
}
}
.main {
height: calc(
100vh - var(--title-bar-height) - var(--snippets-view-header-top-height) -
var(--snippets-view-footer-height)
);
height: calc(100% - v-bind(footerHeight));
}
.footer {
height: 30px;
Expand Down
54 changes: 46 additions & 8 deletions src/renderer/components/sidebar/SidebarListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
:class="{
'is-selected': isSelected,
'is-focused': isFocused,
'is-system': system
'is-system': isSystem,
'is-tag': isTag
}"
@click="isFocused = true"
@contextmenu="onClickContextMenu"
Expand All @@ -32,38 +33,46 @@ import { onClickOutside } from '@vueuse/core'
import { ipc } from '@/electron'
import type {
ContextMenuPayload,
ContextMenuResponse
ContextMenuResponse,
ContextMenuType
} from '@shared/types/main'
import { useFolderStore } from '@/store/folders'
import { useTagStore } from '@/store/tags'
import type { FolderTree } from '@shared/types/main/db'
import { useSnippetStore } from '@/store/snippets'

interface Props {
id?: string
icon?: FunctionalComponent
system?: boolean
isSystem?: boolean
nested?: boolean
open?: boolean
isSelected?: boolean
isTag?: boolean
isFolder?: boolean
model?: FolderTree
alias?: ContextMenuType
name?: string
}

const props = withDefaults(defineProps<Props>(), {
system: false,
nested: false,
open: false
})

const folderStore = useFolderStore()
const snippetStore = useSnippetStore()
const tagStore = useTagStore()

const isFocused = ref(false)
const itemRef = ref()

const onClickContextMenu = async () => {
if (props.id) {
if (props.isFolder) {
const { action, type, data } = await ipc.invoke<
ContextMenuResponse,
ContextMenuPayload
>('context-menu:folder', {
>('context-menu:library', {
name: props.model?.name,
type: 'folder',
data: JSON.parse(JSON.stringify(props.model))
Expand All @@ -80,7 +89,35 @@ const onClickContextMenu = async () => {

if (action === 'update:language') {
console.log(data)
await folderStore.patchFoldersById(props.id, { defaultLanguage: data })
await folderStore.patchFoldersById(props.id!, { defaultLanguage: data })
}
}

if (props.isTag) {
const { action, type, data } = await ipc.invoke<
ContextMenuResponse,
ContextMenuPayload
>('context-menu:library', {
name: props.name,
type: 'tag',
data: {}
})

if (action === 'delete') {
await tagStore.deleteTagById(props.id!)

if (props.id === tagStore.selectedId) {
tagStore.selectedId = undefined
}

if (tagStore.tags.length) {
const firstTagId = tagStore.tags[0].id
tagStore.selectedId = firstTagId
snippetStore.setSnippetsByTagId(firstTagId)
} else {
snippetStore.selected = undefined
snippetStore.snippets = []
}
}
}
}
Expand All @@ -99,7 +136,8 @@ onClickOutside(itemRef, () => {
width: 100%;
z-index: 2;
user-select: none;
&.is-system {
&.is-system,
&.is-tag {
padding-left: var(--spacing-sm);
padding-right: var(--spacing-sm);
}
Expand Down
Loading