Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"fs-extra": "^10.0.1",
"highlight.js": "^11.5.1",
"interactjs": "^1.10.11",
"lodash": "^4.17.21",
"lowdb": "^3.0.0",
"markdown-it": "^12.3.2",
"markdown-it-link-attributes": "^4.0.0",
Expand Down
3 changes: 2 additions & 1 deletion src/main/preload.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from 'electron'
import { isDbExist, migrate, move } from './services/db'
import { isDbExist, migrate, migrateFromSnippetsLab, move } from './services/db'
import { store } from './store'
import type { ElectronBridge } from '@shared/types/main'
import { version } from '../../package.json'
Expand Down Expand Up @@ -29,6 +29,7 @@ contextBridge.exposeInMainWorld('electron', {
},
db: {
migrate: path => migrate(path),
migrateFromSnippetsLab: path => migrateFromSnippetsLab(path),
move: (from, to) => move(from, to),
isExist: path => isDbExist(path)
},
Expand Down
118 changes: 116 additions & 2 deletions src/main/services/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import fs from 'fs-extra'
import readline from 'readline'
import { nestedToFlat } from '../../utils'
import { nanoid } from 'nanoid'
import type { Folder, Snippet, Tag } from '@shared/types/main/db'
import { oldLanguageMap } from '../../../renderer/components/editor/languages'
import type { DB, Folder, Snippet, Tag } from '@shared/types/main/db'
import {
oldLanguageMap,
languages
} from '../../../renderer/components/editor/languages'
import { snakeCase } from 'lodash'

const DB_NAME = 'db.json'

Expand Down Expand Up @@ -207,3 +211,113 @@ export const migrate = async (path: string) => {
writeToFile(db)
console.log('Migrate is done')
}

export const migrateFromSnippetsLab = (path: string) => {
interface SLFragment {
Content: string
'Date Created': string
'Date Modified': string
Note: string
Title: string
Language: string
}
interface SLSnippet {
'Date Created': string
'Date Modified': string
Folder: string
Title: string
Fragments: SLFragment[]
Tags: string[]
}

interface SnippetsLabDbJSON {
Snippets: SLSnippet[]
}

const INBOX = 'Uncategorized'

const file = fs.readFileSync(path, 'utf-8')
const json = JSON.parse(file) as SnippetsLabDbJSON

const folders = new Set<string>()
const tags = new Set<string>()

const db: DB = {
folders: [...DEFAULT_SYSTEM_FOLDERS],
snippets: [],
tags: []
}

json.Snippets.forEach(i => {
if (i.Folder) folders.add(i.Folder)

if (i.Tags.length) {
i.Tags.forEach(t => tags.add(t))
}
})

folders.forEach(i => {
if (i === INBOX) return
db.folders.push({
id: nanoid(8),
name: i,
defaultLanguage: 'plain_text',
parentId: null,
isOpen: false,
isSystem: false,
createdAt: new Date().valueOf(),
updatedAt: new Date().valueOf()
})
})

tags.forEach(i => {
db.tags.push({
id: nanoid(8),
name: i,
createdAt: new Date().valueOf(),
updatedAt: new Date().valueOf()
})
})

json.Snippets.forEach(i => {
const folderId = db.folders.find(f => f.name === i.Folder)?.id || ''
const tagsIds: string[] = []

if (i.Tags.length) {
i.Tags.forEach(t => {
const id = db.tags.find(_t => _t.name === t)?.id
if (id) tagsIds.push(id)
})
}

const snippet: Snippet = {
id: nanoid(8),
name: i.Title,
content: [],
folderId,
tagsIds,
isDeleted: false,
isFavorites: false,
createdAt: new Date(i['Date Created']).valueOf(),
updatedAt: new Date(i['Date Modified']).valueOf()
}

if (i.Fragments.length) {
i.Fragments.forEach(f => {
const _language = snakeCase(f.Language.toLowerCase())
const language = languages.find(i => i.value === _language)?.value

snippet.content.push({
label: f.Title,
value: f.Content,
language: language || 'plain_text'
})
})
}

db.snippets.push(snippet)
})

writeToFile(db)
console.log('Migrate is done')
}
9 changes: 6 additions & 3 deletions src/main/services/ipc/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { MessageBoxRequest } from '@shared/types/main'
import type { DialogRequest, MessageBoxRequest } from '@shared/types/main'
import { dialog, ipcMain } from 'electron'

export const subscribeToDialog = () => {
ipcMain.handle('main:open-dialog', () => {
ipcMain.handle<DialogRequest, any>('main:open-dialog', (event, payload) => {
return new Promise<string>(resolve => {
const { properties, filters } = payload

const dir = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory']
properties: properties || ['openDirectory', 'createDirectory'],
filters: filters || [{ name: '*', extensions: ['json'] }]
})

if (dir) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,21 @@
</AppFormItem>
<AppFormItem label="Migrate">
<AppButton @click="onClickMigrate">
Open folder
From massCode v1.0
</AppButton>
<AppButton @click="onClickMigrateFromSnippetsLab">
From SnippetsLab
</AppButton>
<template #desc>
To migrate from v1 select the folder containing the database files.
To migrate from massCode v1.0 select the folder containing the
database files.
<p>To migrate from SnippetsLab select JSON file.</p>
<p>
Some Limitations. During migration from SnippetsLab, snippets with
unsupported languages will be set to default Plain Text. Also since
JSON file does not represent nesting for folders, all folders will
be first level.
</p>
</template>
</AppFormItem>
<AppFormItem label="Count">
Expand All @@ -36,7 +47,7 @@
import { ipc, store, db, track } from '@/electron'
import { useFolderStore } from '@/store/folders'
import { useSnippetStore } from '@/store/snippets'
import type { MessageBoxRequest } from '@shared/types/main'
import type { MessageBoxRequest, DialogRequest } from '@shared/types/main'
import { ref } from 'vue'

const snippetStore = useSnippetStore()
Expand Down Expand Up @@ -95,12 +106,20 @@ const onClickMigrate = async () => {

try {
const path = await ipc.invoke<any, string>('main:open-dialog', {})

if (!path) return

await db.migrate(path)

ipc.invoke('main:restart-api', {})

resetStore()
await snippetStore.getSnippets()

ipc.invoke('main:notification', {
body: 'DB successfully migrated.'
})
snippetStore.getSnippets()

track('app/migrate')
} catch (err) {
const e = err as Error
Expand All @@ -111,22 +130,64 @@ const onClickMigrate = async () => {
}
}

const onClickMigrateFromSnippetsLab = async () => {
const state = await ipc.invoke<MessageBoxRequest, boolean>(
'main:open-message-box',
{
message: 'Are you sure you want to migrate from SnippetsLab',
detail: 'During migrate, the current library will be overwritten.',
buttons: ['Confirm', 'Cancel']
}
)

if (!state) return

try {
const path = await ipc.invoke<DialogRequest, string>('main:open-dialog', {
properties: ['openFile']
})

if (!path) return

db.migrateFromSnippetsLab(path)

ipc.invoke('main:restart-api', {})

resetStore()
await snippetStore.getSnippets()

ipc.invoke('main:notification', {
body: 'DB successfully migrated.'
})

track('app/migrate', 'from-snippets-lab')
} catch (err) {
const e = err as Error
ipc.invoke('main:notification', {
body: e.message
})
console.error(err)
}
}

const setStorageAndRestartApi = (path: string, reset?: boolean) => {
storagePath.value = path
store.preferences.set('storagePath', path)

if (reset) {
store.app.delete('selectedFolderAlias')
store.app.delete('selectedFolderId')
store.app.delete('selectedFolderIds')
store.app.delete('selectedSnippetId')

snippetStore.$reset()
folderStore.$reset()
}
if (reset) resetStore()

ipc.invoke('main:restart-api', {})
}

const resetStore = () => {
store.app.delete('selectedFolderAlias')
store.app.delete('selectedFolderId')
store.app.delete('selectedFolderIds')
store.app.delete('selectedSnippetId')

snippetStore.$reset()
folderStore.$reset()
}
</script>

<style lang="scss" scoped></style>
2 changes: 1 addition & 1 deletion src/renderer/views/Preferences.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
name="Storage"
value="storage"
>
<Storage />
<StoragePreferences />
</AppMenuItem>
<AppMenuItem
name="Editor"
Expand Down
8 changes: 7 additions & 1 deletion src/shared/types/main/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IpcRendererEvent } from 'electron'
import type { IpcRendererEvent, OpenDialogOptions } from 'electron'
import type { TrackEvents } from '../main/analytics'
import type { AppStore, PreferencesStore } from './store'

Expand Down Expand Up @@ -77,6 +77,11 @@ export interface MessageBoxRequest {
buttons: string[]
}

export interface DialogRequest {
properties?: OpenDialogOptions['properties']
filters?: OpenDialogOptions['filters']
}

interface EventCallback {
(event?: IpcRendererEvent, ...args: any[]): void
}
Expand Down Expand Up @@ -107,6 +112,7 @@ export interface ElectronBridge {
}
db: {
migrate: (path: string) => Promise<void>
migrateFromSnippetsLab: (path: string) => void
move: (from: string, to: string) => Promise<void>
isExist: (path: string) => boolean
}
Expand Down