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"