diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c4f0dd70..b3ad2aec 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,12 +1,14 @@ --- name: Bug report about: Create a report to help us improve -title: "[BUG]" +title: "[BUG] Issue title" labels: '' assignees: '' --- +> Issues that do not follow to this template will be closed without review. + **Describe the bug** A clear and concise description of what the bug is. diff --git a/CHANGELOG.md b/CHANGELOG.md index 46ab3cea..36027481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# [2.0.0-beta.4](https://github.com/massCodeIO/massCode/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2022-04-12) + + +### Bug Fixes + +* **editor:** undo/redo stack ([84096c4](https://github.com/massCodeIO/massCode/commit/84096c47bcf88b49229997d592f473f23812672e)) +* **snippets:** sort in 'All snippets' ([ab799d5](https://github.com/massCodeIO/massCode/commit/ab799d5df478bed659d56cb6529c1dd589355c0f)) + + +### Features + +* add editor preferences ([#18](https://github.com/massCodeIO/massCode/issues/18)) ([d1fe23f](https://github.com/massCodeIO/massCode/commit/d1fe23fd510445426424bef85df2e3cf01c086e4)) +* **snippets:** add markdown preview ([#15](https://github.com/massCodeIO/massCode/issues/15)) ([c208871](https://github.com/massCodeIO/massCode/commit/c2088712ffbc38c2ce2593ec50dbaeaa6291bd7a)) + + + # [2.0.0-beta.3](https://github.com/massCodeIO/massCode/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2022-04-11) diff --git a/README.md b/README.md index c92a7b39..0502e7bb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

-

massCode next

+

massCode

Built with Electron, Vue 3 & Ace Editor. @@ -29,10 +29,35 @@ The best support right now is your star for the project. You can also help spread the word about the app in the public. -## Feature for now + + +## Features +### Organization +massCode allows you to organize snippets using multi-level folders as well as tags. Each snippet has fragments - tabs, which gives even greater level of organization. + +### Editor +A snippet manager must not only provide organization of snippets but also have a good code editor. That's why under the hood of massCode there's [Ace](https://microsoft.github.io/monaco-editor). Ace is a high performance code editor which supports syntax highlighting for over 170 languages. We also added a [Prettier](https://prettier.io/) to code formatter. + +### Markdown +massCode allows you to write in Markdown and also provide syntax highlighting inside a code block. And of course there is a preview. + +### Search +It is impossible to imagine a productive snippets manager without quick access to snippets. Therefore massCode has a fast full-text search with highlighting of the search query. + +### Autosave +massCode automatically saves any changes you make during work, so you don't have to worry about losing changes. + +### Sync +You can use any service that provides cloud synchronization, such as iCloud Drive, Google Drive, Dropbox or other similar. + +### Database +massCode uses a simple JSON to store your data. The database files are on your local computer. + +### API Server +Coming soon. ## Overview @@ -45,4 +70,10 @@ massCode allows you to organize snippets using multi-level folders as well as ta - [Discussions](https://github.com/massCodeIO/massCode/discussions). ## Other -You can also [download](https://github.com/antonreshetov/massCode) massCode v1. \ No newline at end of file +You can also [download](https://github.com/antonreshetov/massCode) massCode v1. + +## License + +[AGPL-3.0](https://github.com/massCodeIO/massCode/blob/master/LICENSE) + +Copyright (c) 2019-present, Anton Reshetov. \ No newline at end of file diff --git a/package.json b/package.json index eb74158f..b4e7579a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "masscode", "productName": "massCode", - "version": "2.0.0-beta.4", + "version": "2.0.0-beta.5", "description": "A free and open source code snippets manager for developers", "license": "AGPL-3.0", "main": "build/src/main/index.js", @@ -11,7 +11,8 @@ "dev:server": "node build/scripts/dev-server.js", "dev:vue-devtools": "vue-devtools", "build": "npm run build:ts && node build/scripts/build.js ", - "build:ts": "vue-tsc --noEmit --skipLibCheck && tsc -p tsconfig.electron.json", + "build:ts": "tsc -p tsconfig.electron.json", + "ts-check:vue": "vue-tsc --noEmit --skipLibCheck", "lint": "eslint --ext .js,.ts,.vue . src", "lint:fix": "eslint --ext .js,.ts,.vue . --fix src", "release": "bumpp -c 'build: release v' -t", @@ -40,6 +41,7 @@ "electron-store": "^8.0.1", "fs-extra": "^10.0.1", "highlight.js": "^11.5.1", + "interactjs": "^1.10.11", "lowdb": "^3.0.0", "markdown-it": "^12.3.2", "markdown-it-link-attributes": "^4.0.0", @@ -62,6 +64,7 @@ "@types/markdown-it": "^12.2.3", "@types/markdown-it-link-attributes": "^3.0.1", "@types/node": "^17.0.4", + "@types/prettier": "^2.6.0", "@types/sanitize-html": "^2.6.2", "@types/webpack": "^5.28.0", "@typescript-eslint/eslint-plugin": "^5.8.0", diff --git a/src/main/menu/main.ts b/src/main/menu/main.ts index 2e8931c2..69e6c92d 100644 --- a/src/main/menu/main.ts +++ b/src/main/menu/main.ts @@ -1,6 +1,6 @@ import { createMenu } from '../components/menu' import type { MenuItemConstructorOptions } from 'electron' -import { dialog, app, BrowserWindow } from 'electron' +import { shell, dialog, app, BrowserWindow } from 'electron' import { version, author } from '../../../package.json' import os from 'os' @@ -78,10 +78,6 @@ const appMenu: MenuItemConstructorOptions[] = [ ] const helpMenu: MenuItemConstructorOptions[] = [ - { - label: 'Toogle Dev tools', - role: 'toggleDevTools' - }, { label: 'About', click () { @@ -100,6 +96,62 @@ const helpMenu: MenuItemConstructorOptions[] = [ ` }) } + }, + { + label: 'Website', + click: () => { + shell.openExternal('https://masscode.io') + } + }, + { + label: 'Change Log', + click: () => { + shell.openExternal( + 'https://github.com/massCodeIO/massCode/blob/master/CHANGELOG.md' + ) + } + }, + { + label: 'Documentation', + click: () => { + shell.openExternal('https://masscode.io/documentation') + } + }, + { + label: 'View in GitHub', + click: () => { + shell.openExternal('https://github.com/massCodeIO/massCode') + } + }, + { + label: 'Report Issue', + click: () => { + shell.openExternal( + 'https://github.com/massCodeIO/massCode/issues/new/choose' + ) + } + }, + { + type: 'separator' + }, + { + label: 'Donate', + click: () => { + shell.openExternal('https://opencollective.com/masscode') + } + }, + { + label: 'Twitter', + click: () => { + shell.openExternal('https://twitter.com/anton_reshetov') + } + }, + { + type: 'separator' + }, + { + label: 'Toggle Developer Tools', + role: 'toggleDevTools' } ] @@ -110,15 +162,81 @@ if (isDev) { }) } +const fileMenu: MenuItemConstructorOptions[] = [ + { + label: 'New Snippet', + accelerator: 'CommandOrControl+N', + click: () => { + BrowserWindow.getFocusedWindow()?.webContents.send( + 'main-menu:new-snippet' + ) + } + }, + { + label: 'New Fragment', + accelerator: 'CommandOrControl+T', + click: () => { + BrowserWindow.getFocusedWindow()?.webContents.send( + 'main-menu:new-fragment' + ) + } + }, + { + label: 'New Folder', + accelerator: 'CommandOrControl+Shift+N', + click: () => { + BrowserWindow.getFocusedWindow()?.webContents.send('main-menu:new-folder') + } + } +] + +const editorMenu: MenuItemConstructorOptions[] = [ + { + label: 'Copy Snippet to Clipboard', + accelerator: 'Shift+CommandOrControl+C', + click: () => { + BrowserWindow.getFocusedWindow()?.webContents.send( + 'main-menu:copy-snippet' + ) + } + }, + { + label: 'Format', + accelerator: 'Shift+CommandOrControl+F', + click: () => { + BrowserWindow.getFocusedWindow()?.webContents.send( + 'main-menu:format-snippet' + ) + } + }, + { + label: 'Preview Markdown', + accelerator: 'Shift+CommandOrControl+M', + click: () => { + BrowserWindow.getFocusedWindow()?.webContents.send( + 'main-menu:preview-markdown' + ) + } + } +] + const menuItems: MenuItemConstructorOptions[] = [ { label: 'massCode', submenu: isMac ? appMenuMac : appMenu }, + { + label: 'File', + submenu: fileMenu + }, { label: 'Edit', role: 'editMenu' }, + { + label: 'Editor', + submenu: editorMenu + }, { label: 'Help', submenu: helpMenu diff --git a/src/main/store/module/app.ts b/src/main/store/module/app.ts index 23ad09c9..0253dfa8 100644 --- a/src/main/store/module/app.ts +++ b/src/main/store/module/app.ts @@ -6,6 +6,8 @@ export default new Store({ cwd: 'v2', defaults: { - bounds: {} + bounds: {}, + sidebarWidth: 180, + snippetListWidth: 250 } }) diff --git a/src/main/store/module/preferences.ts b/src/main/store/module/preferences.ts index 7706ea76..83eb9515 100644 --- a/src/main/store/module/preferences.ts +++ b/src/main/store/module/preferences.ts @@ -19,7 +19,10 @@ export default new Store({ fontFamily: 'SF Mono, Consolas, Menlo', fontSize: 12, showInvisibles: false, - tabSize: 2 + tabSize: 2, + trailingComma: 'none', + semi: false, + singleQuote: true } } }) diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 914aff53..60e8aa5c 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -16,10 +16,17 @@ + diff --git a/src/renderer/components/sidebar/SidebarList.vue b/src/renderer/components/sidebar/SidebarList.vue index f51ccc31..0fd6e403 100644 --- a/src/renderer/components/sidebar/SidebarList.vue +++ b/src/renderer/components/sidebar/SidebarList.vue @@ -26,7 +26,7 @@

@@ -46,8 +46,9 @@ diff --git a/src/renderer/composable/index.ts b/src/renderer/composable/index.ts index 1b1c2d6c..b1a8e4d4 100644 --- a/src/renderer/composable/index.ts +++ b/src/renderer/composable/index.ts @@ -1,10 +1,66 @@ -import { createFetch } from '@vueuse/core' +import { createFetch, useClipboard } from '@vueuse/core' import mitt from 'mitt' import type { EmitterEvents } from '@shared/types/renderer/composable' import { API_PORT } from '../../main/config' +import { useFolderStore } from '@/store/folders' +import { useSnippetStore } from '@/store/snippets' +import { ipc, track } from '@/electron' +import type { NotificationRequest } from '@shared/types/main' export const useApi = createFetch({ baseUrl: `http://localhost:${API_PORT}` }) export const emitter = mitt() + +export const onAddNewSnippet = async () => { + const folderStore = useFolderStore() + const snippetStore = useSnippetStore() + + if (folderStore.selectedAlias !== undefined) return + if (!folderStore.selectedId) return + + await snippetStore.addNewSnippet() + await snippetStore.getSnippetsByFolderIds(folderStore.selectedIds!) + await snippetStore.getSnippets() + + emitter.emit('focus:snippet-name', true) + track('snippets/add-new') +} + +export const onAddNewFragment = () => { + const snippetStore = useSnippetStore() + + snippetStore.addNewFragmentToSnippetsById(snippetStore.selectedId!) + snippetStore.fragment = snippetStore.fragmentCount! + + track('snippets/add-fragment') +} + +export const onAddNewFolder = async () => { + const folderStore = useFolderStore() + const snippetStore = useSnippetStore() + + const folder = await folderStore.addNewFolder() + snippetStore.selected = undefined + + emitter.emit('scroll-to:folder', folder.id) + track('folders/add-new') +} + +export const onCopySnippet = () => { + const snippetStore = useSnippetStore() + + const { copy } = useClipboard({ source: snippetStore.currentContent }) + copy() + + ipc.invoke('main:notification', { + body: 'Snippet copied' + }) + track('snippets/copy') +} + +export const setScrollPosition = (el: HTMLElement, offset: number) => { + const ps = el.querySelector('.ps') + if (ps) ps.scrollTop = offset +} diff --git a/src/renderer/store/app.ts b/src/renderer/store/app.ts index 094ffed3..97c513ba 100644 --- a/src/renderer/store/app.ts +++ b/src/renderer/store/app.ts @@ -8,7 +8,10 @@ const EDITOR_DEFAULTS: EditorSettings = { fontSize: 12, showInvisibles: false, tabSize: 2, - wrap: 'free' + wrap: 'free', + trailingComma: 'none', + semi: false, + singleQuote: true } export const useAppStore = defineStore('app', { @@ -18,6 +21,8 @@ export const useAppStore = defineStore('app', { showTags: true, sizes: { titlebar: 15, + sidebar: 180, + snippetList: 250, editor: { titleHeight: 34, fragmentsHeight: 25, diff --git a/src/renderer/store/folders.ts b/src/renderer/store/folders.ts index c296b982..8f23314b 100644 --- a/src/renderer/store/folders.ts +++ b/src/renderer/store/folders.ts @@ -46,6 +46,7 @@ export const useFolderStore = defineStore('folders', { await this.getFolders() this.selectId(data.value.id) await snippetStore.getSnippetsByFolderIds(this.selectedIds!) + return data.value as Folder }, async patchFoldersById (id: string, body: Partial) { body.updatedAt = new Date().valueOf() diff --git a/src/renderer/views/Main.vue b/src/renderer/views/Main.vue index c781680c..9c9664ce 100644 --- a/src/renderer/views/Main.vue +++ b/src/renderer/views/Main.vue @@ -12,12 +12,16 @@ import { useFolderStore } from '@/store/folders' import { useSnippetStore } from '@/store/snippets' import { useTagStore } from '@/store/tags' import { useAppStore } from '@/store/app' +import { computed } from 'vue' const folderStore = useFolderStore() const snippetStore = useSnippetStore() const tagStore = useTagStore() const appStore = useAppStore() +const sidebarWidth = computed(() => appStore.sizes.sidebar + 'px') +const snippetListWidth = computed(() => appStore.sizes.snippetList + 'px') + const init = async () => { const storedFolderId = store.app.get('selectedFolderId') const storedFolderAlias = store.app.get('selectedFolderAlias') @@ -62,7 +66,7 @@ track('main') height: 100vh; background-color: var(--color-bg); overflow: hidden; - grid-template-columns: var(--sidebar-width) var(--snippets-list-width) 1fr; + grid-template-columns: v-bind(sidebarWidth) v-bind(snippetListWidth) 1fr; } .update-available { position: absolute; diff --git a/src/renderer/views/Preferences.vue b/src/renderer/views/Preferences.vue index 56b4daaa..6293019c 100644 --- a/src/renderer/views/Preferences.vue +++ b/src/renderer/views/Preferences.vue @@ -45,16 +45,15 @@ track('preferences') margin: 0; } margin-top: var(--title-bar-height); - padding: var(--spacing-sm); } .title { + padding: var(--spacing-sm); display: flex; align-items: center; justify-content: space-between; } .body { - padding-top: var(--spacing-sm); + padding: 0 0 var(--spacing-sm) var(--spacing-sm); display: grid; - grid-template-columns: 200px 1fr; } diff --git a/src/shared/types/main/analytics.d.ts b/src/shared/types/main/analytics.d.ts index 50dc95dd..ebb59653 100644 --- a/src/shared/types/main/analytics.d.ts +++ b/src/shared/types/main/analytics.d.ts @@ -13,6 +13,7 @@ type SnippetEvents = | 'move-to-trash' | 'set-language' | 'search' + | 'format' type FolderEvents = 'add-new' | 'delete' | 'set-language' type TagEvents = 'add-new' | 'delete' type AppEvents = @@ -20,6 +21,7 @@ type AppEvents = | 'open-storage' | 'migrate' | 'update' + | 'install' | 'empty-trash' type TrackSnippetEvents = CombineWith diff --git a/src/shared/types/main/index.d.ts b/src/shared/types/main/index.d.ts index 6c9a6a8c..9020dfb9 100644 --- a/src/shared/types/main/index.d.ts +++ b/src/shared/types/main/index.d.ts @@ -29,7 +29,15 @@ export type ContextMenuType = | 'favorites' | 'tag' -type MainMenuAction = 'preferences' | 'new-snippet' +type MainMenuAction = + | 'preferences' + | 'new-snippet' + | 'copy-snippet' + | 'format-snippet' + | 'new-fragment' + | 'new-folder' + | 'search' + | 'preview-markdown' type MainAction = | 'restart' diff --git a/src/shared/types/main/store.d.ts b/src/shared/types/main/store.d.ts index 22b2aabb..20b321ed 100644 --- a/src/shared/types/main/store.d.ts +++ b/src/shared/types/main/store.d.ts @@ -4,6 +4,9 @@ export interface AppStore { selectedFolderAlias?: string selectedFolderIds?: string[] selectedSnippetId?: string + sidebarWidth: number + snippetListWidth: number + version?: string } interface Editor { @@ -12,6 +15,9 @@ interface Editor { showInvisibles: boolean tabSize: number wrap: string + trailingComma: 'all' | 'none' | 'es5' + semi: boolean + singleQuote: boolean } export interface PreferencesStore { storagePath: string diff --git a/src/shared/types/renderer/composable/index.d.ts b/src/shared/types/renderer/composable/index.d.ts index 9f5eb6aa..f1383e4d 100644 --- a/src/shared/types/renderer/composable/index.d.ts +++ b/src/shared/types/renderer/composable/index.d.ts @@ -1,4 +1,6 @@ export type EmitterEvents = { 'focus:snippet-name': boolean 'folder:click': any + 'scroll-to:folder': string + 'format-snippet': boolean } diff --git a/src/shared/types/renderer/store/app.d.ts b/src/shared/types/renderer/store/app.d.ts index b7ba2795..154ce014 100644 --- a/src/shared/types/renderer/store/app.d.ts +++ b/src/shared/types/renderer/store/app.d.ts @@ -2,6 +2,8 @@ import type { Ace } from 'ace-builds' export interface AppSizes { titlebar: number + sidebar: number + snippetList: number editor: { titleHeight: number fragmentsHeight: number @@ -16,6 +18,9 @@ export interface EditorSettings { fontFamily: string wrap: Ace.EditSessionOptions['wrap'] tabSize: number + trailingComma: 'all' | 'none' | 'es5' + semi: boolean + singleQuote: boolean } export interface State { platform: NodeJS.Platform diff --git a/yarn.lock b/yarn.lock index 596d16cc..30564de1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1342,6 +1342,11 @@ svgstore "^3.0.0-2" uuid "^3.3.2" +"@interactjs/types@1.10.11": + version "1.10.11" + resolved "https://registry.yarnpkg.com/@interactjs/types/-/types-1.10.11.tgz#29be25d503f9c7842df062fa3cda5b044a47cf2a" + integrity sha512-YRsVFWjL8Gkkvlx3qnjeaxW4fnibSJ9791g8BA7Pv5ANByI64WmtR1vU7A2rXcrOn8XvyCEfY0ss1s8NhZP+MA== + "@josephg/resolvable@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" @@ -1846,6 +1851,11 @@ "@types/node" "*" xmlbuilder ">=11.0.1" +"@types/prettier@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.0.tgz#efcbd41937f9ae7434c714ab698604822d890759" + integrity sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw== + "@types/q@^1.5.1": version "1.5.5" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" @@ -6958,6 +6968,13 @@ insertion-query@^1.1.0: resolved "https://registry.yarnpkg.com/insertion-query/-/insertion-query-1.1.0.tgz#25c98db5acdc8e012a1bc2ae8f617a8edf82ed5d" integrity sha512-5HZCK1xmD+Cm5q9/Qk1S8tr2BqHtcvseGsg7LKD3WlHRg4OOCl7d6C5raGNXAhfV4NlUcHmAtdJt0b3vJPEuiA== +interactjs@^1.10.11: + version "1.10.11" + resolved "https://registry.yarnpkg.com/interactjs/-/interactjs-1.10.11.tgz#0b9eabe37f1f82fff68e22faad51269b8f2038ea" + integrity sha512-VPUWsGAOPmrZe1YF7Fq/4AIBBZ+3FikZRS8bpzT6VsAfUuhxl/CKJY73IAiZHd3fz9p174CXErn0Qs81XEFICA== + dependencies: + "@interactjs/types" "1.10.11" + internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"