├── .eslintignore ├── dist ├── web │ └── .gitkeep └── electron │ └── .gitkeep ├── static ├── .gitkeep ├── trayTemplate.png ├── trayTemplate@2x.png └── css │ └── hljs │ ├── atom-one-dark.css │ └── atom-one-light.css ├── .prettierrc ├── demo.gif ├── logo.png ├── preview.png ├── src ├── renderer │ ├── event-bus.js │ ├── assets │ │ └── scss │ │ │ ├── main.scss │ │ │ ├── mixins.scss │ │ │ ├── themes.scss │ │ │ ├── vendor.scss │ │ │ ├── base.scss │ │ │ ├── variables.scss │ │ │ └── reset.scss │ ├── components │ │ ├── uikit │ │ │ ├── AppForm.vue │ │ │ ├── AppIcon.vue │ │ │ ├── AppMenuItem.vue │ │ │ ├── AppCheckboxGroup.vue │ │ │ ├── AppFormItem.vue │ │ │ ├── index.js │ │ │ ├── AppButton.vue │ │ │ ├── AppSelect.vue │ │ │ ├── AppAlert.vue │ │ │ ├── AppMenu.vue │ │ │ ├── AppCheckbox.vue │ │ │ ├── AppInput.vue │ │ │ ├── AppPopper.vue │ │ │ └── AppInputTags.vue │ │ ├── tray │ │ │ ├── TraySnippetList.vue │ │ │ └── TraySnippetListItem.vue │ │ ├── preferences │ │ │ ├── Interface.vue │ │ │ ├── Assistant.vue │ │ │ ├── Editor.vue │ │ │ └── Storage.vue │ │ ├── snippets │ │ │ ├── SnippetTabsPane.vue │ │ │ ├── SnippetTabs.vue │ │ │ ├── ActionBar.vue │ │ │ ├── SnippetTabsItem.vue │ │ │ └── SnippetList.vue │ │ ├── sidebar │ │ │ ├── TheSidebar.vue │ │ │ ├── TheTags.vue │ │ │ ├── SidebarList.vue │ │ │ ├── TheLibrary.vue │ │ │ └── TheFolders.vue │ │ └── editor │ │ │ ├── languages.js │ │ │ └── MarkdownPreview.vue │ ├── util │ │ └── helpers.js │ ├── store │ │ ├── index.js │ │ └── modules │ │ │ ├── preferences.js │ │ │ ├── tags.js │ │ │ ├── app.js │ │ │ └── folders.js │ ├── main.js │ ├── router.js │ ├── directives.js │ ├── lib │ │ └── ipcRenderer.js │ ├── views │ │ ├── Main.vue │ │ ├── Preferences.vue │ │ └── Tray.vue │ ├── App.vue │ └── datastore.js ├── main │ ├── lib │ │ ├── index.js │ │ ├── dialog.js │ │ ├── menu.js │ │ ├── touch-bar.js │ │ ├── update-check.js │ │ └── analytics.js │ ├── store │ │ ├── index.js │ │ └── module │ │ │ ├── app.js │ │ │ └── preferences.js │ ├── util │ │ └── platform.js │ ├── index.dev.js │ ├── main.js │ ├── tray.js │ └── index.js └── index.ejs ├── .gitignore ├── .editorconfig ├── jsconfig.json ├── scripts ├── release.sh └── verify-commit.js ├── appveyor.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── COMMIT_CONVENTION.md └── workflows │ └── main.yml ├── .babelrc ├── .eslintrc.js ├── .vscode └── tasks.json ├── .electron-vue ├── terserOptions.js ├── dev-client.js ├── webpack.main.config.js ├── build.js ├── webpack.web.config.js ├── dev-runner.js └── webpack.renderer.config.js ├── README.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/web/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/electron/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonreshetov/massCode/HEAD/demo.gif -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonreshetov/massCode/HEAD/logo.png -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonreshetov/massCode/HEAD/preview.png -------------------------------------------------------------------------------- /src/renderer/event-bus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default new Vue() 4 | -------------------------------------------------------------------------------- /static/trayTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonreshetov/massCode/HEAD/static/trayTemplate.png -------------------------------------------------------------------------------- /static/trayTemplate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonreshetov/massCode/HEAD/static/trayTemplate@2x.png -------------------------------------------------------------------------------- /src/main/lib/index.js: -------------------------------------------------------------------------------- 1 | import menu from './menu' 2 | import dialog from './dialog' 3 | 4 | export { menu, dialog } 5 | -------------------------------------------------------------------------------- /src/main/lib/dialog.js: -------------------------------------------------------------------------------- 1 | import { remote } from 'electron' 2 | 3 | const { dialog } = remote 4 | 5 | export default dialog 6 | -------------------------------------------------------------------------------- /src/renderer/assets/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import './reset'; 2 | @import './variables'; 3 | @import './markdown'; 4 | @import './base'; 5 | @import './vendor'; 6 | @import './themes'; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/web/* 4 | build/* 5 | !build/icons 6 | node_modules/ 7 | npm-debug.log 8 | npm-debug.log.* 9 | thumbs.db 10 | .env 11 | !.gitkeep 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/main/store/index.js: -------------------------------------------------------------------------------- 1 | import app from './module/app' 2 | import preferences from './module/preferences' 3 | 4 | const store = { 5 | app, 6 | preferences 7 | } 8 | 9 | export default store 10 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/renderer/*"], 6 | "@@/*": ["./src/main/*"] 7 | } 8 | }, 9 | "exclude": ["node_modules", ".nuxt", "dist"] 10 | } 11 | -------------------------------------------------------------------------------- /src/main/util/platform.js: -------------------------------------------------------------------------------- 1 | export function convertName (platform) { 2 | if (platform === 'darwin') { 3 | return 'macOS' 4 | } 5 | if (platform === 'win32') { 6 | return 'Windows' 7 | } 8 | if (platform === 'linux') { 9 | return 'Linux' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppForm.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/renderer/components/tray/TraySnippetList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/lib/menu.js: -------------------------------------------------------------------------------- 1 | import { remote } from 'electron' 2 | const { Menu, MenuItem } = remote 3 | 4 | function popup (templates) { 5 | const menu = new Menu() 6 | templates.forEach(item => { 7 | menu.append(new MenuItem(item)) 8 | }) 9 | menu.popup(remote.getCurrentWindow()) 10 | 11 | return menu 12 | } 13 | 14 | const menu = { 15 | popup 16 | } 17 | 18 | export default menu 19 | -------------------------------------------------------------------------------- /src/renderer/util/helpers.js: -------------------------------------------------------------------------------- 1 | export function defaultLibraryQuery (query = {}, id) { 2 | if (id === 'trash') { 3 | query = { isDeleted: true } 4 | } 5 | if (id === 'favorites') { 6 | query = { isFavorites: true } 7 | } 8 | if (id === 'allSnippets') { 9 | query = {} 10 | } 11 | if (id === 'inBox') { 12 | query = { folderId: null } 13 | } 14 | return query 15 | } 16 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env sh 2 | set -e 3 | CURRENT=$(node -p "require('./package.json').version") 4 | echo "Current $CURRENT" 5 | echo "Enter release version: " 6 | read VERSION 7 | 8 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r 9 | echo 10 | if [[ $REPLY =~ ^[Yy]$ ]] 11 | then 12 | git add -A 13 | npm version $VERSION -m "build: release $VERSION" 14 | git push origin master 15 | git push origin --tags 16 | fi -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import app from './modules/app' 5 | import folders from './modules/folders' 6 | import preferences from './modules/preferences' 7 | import snippets from './modules/snippets' 8 | import tags from './modules/tags' 9 | 10 | Vue.use(Vuex) 11 | 12 | export default new Vuex.Store({ 13 | strict: true, 14 | modules: { 15 | app, 16 | folders, 17 | preferences, 18 | snippets, 19 | tags 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.1.{build} 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | image: Visual Studio 2017 8 | platform: 9 | - x64 10 | 11 | cache: 12 | - node_modules 13 | - '%APPDATA%\npm-cache' 14 | - '%USERPROFILE%\.electron' 15 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 16 | 17 | init: 18 | - git config --global core.autocrlf input 19 | 20 | install: 21 | - ps: Install-Product node 8 x64 22 | - git reset --hard HEAD 23 | - yarn 24 | - node --version 25 | 26 | build_script: 27 | - yarn build 28 | 29 | test: off 30 | -------------------------------------------------------------------------------- /src/renderer/assets/scss/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin form-input-default() { 2 | color: var(--color-text); 3 | box-sizing: border-box; 4 | -webkit-appearance: none; 5 | outline: none; 6 | border: none; 7 | line-height: 32px; 8 | height: 32px; 9 | background-color: transparent; 10 | border: 1px solid var(--color-contrast-low); 11 | font-size: var(--text-md); 12 | transition: border-color 0.3s; 13 | &:focus { 14 | transition: border-color 0.3s; 15 | } 16 | &::-webkit-input-placeholder { 17 | color: var(--color-contrast-low); 18 | } 19 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: #['antonreshetov'] 4 | patreon: antonreshetov 5 | open_collective: masscode 6 | ko_fi: antonreshetov 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/antonreshetov'] 13 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppIcon.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "main": { 5 | "presets": [ 6 | [ 7 | "@babel/preset-env", 8 | { 9 | "targets": { "node": "12" } 10 | } 11 | ] 12 | ] 13 | }, 14 | "renderer": { 15 | "presets": [["@babel/preset-env"]] 16 | }, 17 | "web": { 18 | "presets": [["@babel/preset-env"]] 19 | } 20 | }, 21 | "plugins": [ 22 | ["@babel/plugin-transform-runtime", 23 | { 24 | "regenerator": true 25 | } 26 | ], 27 | "@babel/plugin-proposal-optional-chaining" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/assets/scss/themes.scss: -------------------------------------------------------------------------------- 1 | [data-theme='light'] { 2 | .snippet-list-item--selected { 3 | background-color: var(--color-contrast-lower); 4 | &.focus { 5 | background-color: var(--color-primary); 6 | .title, 7 | .meta { 8 | color: #fff; 9 | } 10 | } 11 | } 12 | } 13 | [data-theme='dark'] { 14 | .snippet-list-item--selected { 15 | background-color: var(--color-contrast-lower); 16 | &.focus { 17 | background-color: var(--color-primary); 18 | .title, 19 | .meta { 20 | color: #fff; 21 | } 22 | } 23 | .title, 24 | .meta { 25 | color: #fff; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import store from './store' 4 | import router from './router' 5 | import db from '@/datastore' 6 | import eventBus from '@/event-bus' 7 | import { clickOutside } from './directives' 8 | import UiKit from '@/components/uikit' 9 | 10 | if (!process.env.IS_WEB) Vue.use(require('vue-electron')) 11 | 12 | Vue.config.productionTip = false 13 | Vue.prototype.$db = db 14 | Vue.prototype.$bus = eventBus 15 | 16 | Vue.directive('click-outside', clickOutside) 17 | Vue.use(UiKit) 18 | 19 | /* eslint-disable no-new */ 20 | new Vue({ 21 | store, 22 | router, 23 | render: h => h(App) 24 | }).$mount('#app') 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Describe the bug 2 | 3 | A clear and concise description of what the bug is. 4 | 5 | ## To Reproduce 6 | 7 | Steps to reproduce the behavior: 8 | 9 | **Example:** 10 | 11 | - Go to '...' 12 | - Click on '....' 13 | - Scroll down to '....' 14 | - See error 15 | 16 | ## Expected behavior 17 | 18 | A clear and concise description of what you expected to happen. 19 | 20 | ## Screenshots 21 | 22 | If applicable, add screenshots to help explain your problem. 23 | 24 | ## Environment 25 | 26 | Version: [e.g. 1.0.0] 27 | OS version and name: [e.g. macOS High Sierra 10.13.6] 28 | 29 | ## Additional context 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /src/renderer/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Preferences from './views/Preferences.vue' 4 | import Main from './views/Main.vue' 5 | import Tray from './views/Tray.vue' 6 | 7 | Vue.use(Router) 8 | 9 | export default new Router({ 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'main', 14 | component: Main 15 | }, 16 | { 17 | path: '/preferences', 18 | name: 'preferences', 19 | component: Preferences 20 | }, 21 | { 22 | path: '/tray', 23 | name: 'tray', 24 | component: Tray 25 | }, 26 | { 27 | path: '*', 28 | redirect: '/' 29 | } 30 | ] 31 | }) 32 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppMenuItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 38 | 39 | 43 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | parserOptions: { 8 | parser: 'babel-eslint' 9 | }, 10 | extends: ['plugin:vue/recommended', '@vue/standard'], 11 | globals: { 12 | __static: true 13 | }, 14 | rules: { 15 | 'vue/component-name-in-template-casing': [ 16 | 'error', 17 | 'PascalCase', 18 | { registeredComponentsOnly: false } 19 | ], 20 | 'vue/max-attributes-per-line': [ 21 | 'error', 22 | { 23 | singleline: 1, 24 | multiline: { 25 | max: 1, 26 | allowFirstLine: false 27 | } 28 | } 29 | ], 30 | 'arrow-parens': 0, 31 | 'no-console': 0, 32 | 'import/order': 0 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/components/preferences/Interface.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/store/module/app.js: -------------------------------------------------------------------------------- 1 | import Store from 'electron-store' 2 | 3 | const app = new Store({ 4 | name: 'app', 5 | cwd: 'massCode', 6 | 7 | schema: { 8 | bounds: { 9 | default: null 10 | }, 11 | sidebarWidth: { 12 | default: 180 13 | }, 14 | snippetListWidth: { 15 | default: 220 16 | }, 17 | selectedFolderId: { 18 | default: 'inBox' 19 | }, 20 | selectedFolderIds: { 21 | default: null 22 | }, 23 | selectedSnippetId: { 24 | default: null 25 | }, 26 | snippetsSort: { 27 | default: 'updateAt' 28 | }, 29 | install: { 30 | default: null 31 | }, 32 | updateAvailable: { 33 | default: false 34 | }, 35 | nextVersionNotify: { 36 | default: false 37 | } 38 | } 39 | }) 40 | 41 | export default app 42 | -------------------------------------------------------------------------------- /src/main/index.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically and only for development. It installs 3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to 4 | * modify this file, but it can be used to extend your development 5 | * environment. 6 | */ 7 | 8 | /* eslint-disable */ 9 | 10 | // Install `electron-debug` with `devtron` 11 | require('electron-debug')({ showDevTools: true }) 12 | 13 | // Install `vue-devtools` 14 | require('electron').app.on('ready', () => { 15 | let installExtension = require('electron-devtools-installer') 16 | installExtension 17 | .default(installExtension.VUEJS_DEVTOOLS) 18 | .then(() => {}) 19 | .catch(err => { 20 | console.log('Unable to install `vue-devtools`: \n', err) 21 | }) 22 | }) 23 | 24 | // Require `main` process to boot app 25 | require('./index') 26 | -------------------------------------------------------------------------------- /src/main/lib/touch-bar.js: -------------------------------------------------------------------------------- 1 | import { TouchBar } from 'electron' 2 | import { mainWindow } from '../main' 3 | 4 | const { TouchBarButton, TouchBarSpacer } = TouchBar 5 | 6 | const newSnippet = new TouchBarButton({ 7 | label: 'New Snippet', 8 | click: () => { 9 | mainWindow.webContents.send('menu:new-snippet') 10 | } 11 | }) 12 | 13 | const newFolder = new TouchBarButton({ 14 | label: 'New Folder', 15 | click: () => { 16 | mainWindow.webContents.send('menu:new-folder') 17 | } 18 | }) 19 | 20 | const favorites = new TouchBarButton({ 21 | label: 'Favorites', 22 | click: () => { 23 | mainWindow.webContents.send('menu:favorites') 24 | } 25 | }) 26 | 27 | export default new TouchBar({ 28 | items: [ 29 | newSnippet, 30 | newFolder, 31 | new TouchBarSpacer({ size: 'small' }), 32 | favorites 33 | ] 34 | }) 35 | -------------------------------------------------------------------------------- /src/main/lib/update-check.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { mainWindow } from '../main' 3 | 4 | const pkg = require('../../../package.json') 5 | const isDev = process.env.NODE_ENV === 'development' 6 | 7 | function checkForUpdatesAndNotify () { 8 | if (isDev) return 9 | 10 | async function check () { 11 | const currentVersion = pkg.version 12 | const res = await axios.get( 13 | 'https://github.com/antonreshetov/masscode/releases/latest' 14 | ) 15 | 16 | if (res) { 17 | const latest = res.request.socket._httpMessage.path 18 | .split('/') 19 | .pop() 20 | .substring(1) 21 | if (latest !== currentVersion) { 22 | mainWindow.webContents.send('update-available') 23 | } 24 | } 25 | } 26 | 27 | check() 28 | setInterval(check, 1000 * 60 * 15) 29 | } 30 | 31 | export { checkForUpdatesAndNotify } 32 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppCheckboxGroup.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 50 | -------------------------------------------------------------------------------- /src/renderer/components/snippets/SnippetTabsPane.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 41 | 42 | 51 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppFormItem.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 53 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/index.js: -------------------------------------------------------------------------------- 1 | import AppButton from './AppButton.vue' 2 | import AppCheckbox from './AppCheckbox.vue' 3 | import AppCheckboxGroup from './AppCheckboxGroup.vue' 4 | import AppForm from './AppForm.vue' 5 | import AppFormItem from './AppFormItem.vue' 6 | import AppIcon from './AppIcon.vue' 7 | import AppInput from './AppInput.vue' 8 | import AppMenu from './AppMenu.vue' 9 | import AppMenuItem from './AppMenuItem.vue' 10 | import AppSelect from './AppSelect.vue' 11 | import AppInputTags from './AppInputTags.vue' 12 | import AppPopper from './AppPopper.vue' 13 | 14 | const components = [ 15 | AppButton, 16 | AppCheckbox, 17 | AppCheckboxGroup, 18 | AppForm, 19 | AppFormItem, 20 | AppIcon, 21 | AppInput, 22 | AppMenu, 23 | AppMenuItem, 24 | AppSelect, 25 | AppInputTags, 26 | AppPopper 27 | ] 28 | 29 | export default { 30 | install (Vue, options = {}) { 31 | components.forEach(component => { 32 | Vue.component(component.name, component) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/renderer/directives.js: -------------------------------------------------------------------------------- 1 | export const clickOutside = { 2 | bind (el, binding, vNode) { 3 | if (typeof binding.value !== 'function') { 4 | const compName = vNode.context.name 5 | let warn = `[Vue-click-outside:] provided expression '${binding.expression}' is not a function, but has to be` 6 | if (compName) { 7 | warn += `Found in component '${compName}'` 8 | } 9 | 10 | console.warn(warn) 11 | } 12 | // Define Handler and cache it on the element 13 | const bubble = binding.modifiers.bubble 14 | const handler = e => { 15 | if (bubble || (!el.contains(e.target) && el !== e.target)) { 16 | binding.value(e) 17 | } 18 | } 19 | el.__vueClickOutside__ = handler 20 | 21 | // add Event Listeners 22 | document.addEventListener('click', handler) 23 | }, 24 | 25 | unbind (el, binding) { 26 | // Remove Event Listeners 27 | document.removeEventListener('click', el.__vueClickOutside__) 28 | el.__vueClickOutside__ = null 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Eslint current file", 8 | "type": "shell", 9 | "command": "npx eslint --fix ${file}", 10 | "problemMatcher": [ 11 | "$eslint-stylish" 12 | ], 13 | "presentation": { 14 | "reveal": "never" 15 | } 16 | }, 17 | { 18 | "label": "Prettier current file", 19 | "type": "shell", 20 | "command": "node ./node_modules/prettier/bin-prettier.js --write ${file}", 21 | "problemMatcher": [ 22 | "$eslint-stylish" 23 | ] 24 | "presentation": { 25 | "reveal": "never" 26 | } 27 | }, 28 | { 29 | "label": "Lint", 30 | "dependsOn": [ 31 | "Prettier current file", 32 | "Eslint current file" 33 | ], 34 | "problemMatcher": [ 35 | "$eslint-stylish" 36 | ], 37 | "presentation": { 38 | "reveal": "never" 39 | } 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /scripts/verify-commit.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: ['error', {'devDependencies': true}] */ 2 | const chalk = require('chalk') 3 | 4 | const msgPath = process.env.HUSKY_GIT_PARAMS 5 | const msg = require('fs') 6 | .readFileSync(msgPath, 'utf-8') 7 | .trim() 8 | 9 | const commitRE = /^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\(.+\))?: .{1,50}/ 10 | 11 | if (!commitRE.test(msg)) { 12 | console.log() 13 | /* eslint prefer-template: ['off'] */ 14 | console.error( 15 | ` ${chalk.bgRed.white(' ERROR ')} ${chalk.red( 16 | 'invalid commit message format.' 17 | )}\n\n` + 18 | chalk.red(' Proper commit message format is required. Examples:\n\n') + 19 | ` ${chalk.green("feat: add 'comments' option")}\n` + 20 | ` ${chalk.green( 21 | 'fix(input): handle events on blur (close #28)' 22 | )}\n\n` + 23 | chalk.red(' See ./.github/COMMIT_CONVENTION.md for more details.\n') 24 | ) 25 | process.exit(1) 26 | } else { 27 | console.log(chalk.green('verify commit message success')) 28 | } 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for your contribution to the massCode repo. 2 | Before submitting this PR, please make sure: 3 | 4 | - [ ] Your code builds clean without any errors or warnings 5 | - [ ] Your commits follow the [сonvention](https://github.com/antonreshetov/massCode/blob/master/.github/COMMIT_CONVENTION.md) 6 | 7 | **What kind of change does this PR introduce?** (check at least one) 8 | 9 | - [ ] Bugfix 10 | - [ ] Feature 11 | - [ ] Refactor 12 | - [ ] Build-related changes 13 | - [ ] Other, please describe: 14 | 15 | **Does this PR introduce a breaking change?** (check one) 16 | 17 | - [ ] Yes 18 | - [ ] No 19 | 20 | **The PR fulfills these requirements:** 21 | 22 | - [ ] It's submitted to the `master` branch for v1.x 23 | - [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number) 24 | 25 | If adding a **new feature**, the PR's description includes: 26 | 27 | - [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it) -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppButton.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 27 | 28 | 54 | -------------------------------------------------------------------------------- /.electron-vue/terserOptions.js: -------------------------------------------------------------------------------- 1 | module.exports = options => ({ 2 | terserOptions: { 3 | compress: { 4 | // turn off flags with small gains to speed up minification 5 | arrows: false, 6 | collapse_vars: false, // 0.3kb 7 | comparisons: false, 8 | computed_props: false, 9 | hoist_funs: false, 10 | hoist_props: false, 11 | hoist_vars: false, 12 | inline: false, 13 | loops: false, 14 | negate_iife: false, 15 | properties: false, 16 | reduce_funcs: false, 17 | reduce_vars: false, 18 | switches: false, 19 | toplevel: false, 20 | typeofs: false, 21 | 22 | // a few flags with noticable gains/speed ratio 23 | // numbers based on out of the box vendor bundle 24 | booleans: true, // 0.7kb 25 | if_return: true, // 0.4kb 26 | sequences: true, // 0.7kb 27 | unused: true, // 2.3kb 28 | 29 | // required features to drop conditional branches 30 | conditionals: true, 31 | dead_code: true, 32 | evaluate: true 33 | }, 34 | mangle: { 35 | safari10: true 36 | } 37 | }, 38 | sourceMap: options ? options.sourceMap : false, 39 | cache: true, 40 | parallel: options ? options.parallel : true 41 | }) 42 | -------------------------------------------------------------------------------- /src/renderer/assets/scss/vendor.scss: -------------------------------------------------------------------------------- 1 | @import '~perfect-scrollbar/css/perfect-scrollbar.css'; 2 | 3 | .ps { 4 | $r: &; 5 | &__rail-y { 6 | background-color: rgba(121, 121, 121, .8); 7 | width: 5px; 8 | &:hover { 9 | #{$r}__thumb-y { 10 | width: 5px; 11 | right: 0; 12 | } 13 | } 14 | } 15 | &__rail-x { 16 | background-color: rgba(121, 121, 121, .5); 17 | width: 4px; 18 | &:hover { 19 | height: 4px; 20 | background-color: transparent !important; 21 | opacity: 0.4 !important; 22 | #{$r}__thumb-x { 23 | height: 4px; 24 | right: 0; 25 | } 26 | } 27 | } 28 | &__thumb-y { 29 | border-radius: 0; 30 | background-color: rgba(121, 121, 121, .8); 31 | width: 5px; 32 | right: 0; 33 | } 34 | &__thumb-x { 35 | border-radius: 0; 36 | background-color: rgba(121, 121, 121, .8); 37 | height: 4px; 38 | right: 0; 39 | } 40 | } 41 | 42 | 43 | .draggable-placeholder-inner{ 44 | border: 1px dashed var(--color-contrast-medium); 45 | box-sizing: border-box; 46 | border-radius: 4px; 47 | text-align: center; 48 | padding: 0; 49 | display: flex; 50 | align-items: center; 51 | position: relative; 52 | margin-right: 1px; 53 | 54 | } 55 | 56 | .draggable-placeholder{ 57 | } -------------------------------------------------------------------------------- /src/main/store/module/preferences.js: -------------------------------------------------------------------------------- 1 | import Store from 'electron-store' 2 | import { homedir, platform } from 'os' 3 | 4 | const isWin = platform() === 'win32' 5 | 6 | const defaultPath = isWin ? homedir() + '\\massCode' : homedir() + '/massCode' 7 | const backupPath = isWin ? `${defaultPath}\\backups` : `${defaultPath}/backups` 8 | 9 | const preferences = new Store({ 10 | name: 'preferences', 11 | cwd: 'massCode', 12 | 13 | schema: { 14 | storagePath: { 15 | default: defaultPath 16 | }, 17 | backupPath: { 18 | default: backupPath 19 | }, 20 | theme: { 21 | default: 'dark' 22 | }, 23 | assistant: { 24 | default: true 25 | }, 26 | assistantShortcut: { 27 | default: 'Option+S' 28 | }, 29 | renderWhitespace: { 30 | default: 'none' 31 | }, 32 | wordWrap: { 33 | default: 'off' 34 | }, 35 | tabSize: { 36 | default: 2 37 | }, 38 | insertSpaces: { 39 | default: true 40 | }, 41 | prettierSemi: { 42 | default: false 43 | }, 44 | prettierQuotes: { 45 | default: true 46 | }, 47 | allowAnalytics: { 48 | default: true 49 | } 50 | } 51 | }) 52 | 53 | if (process.platform !== 'darwin') { 54 | preferences.set('assistant', false) 55 | } 56 | 57 | export default preferences 58 | -------------------------------------------------------------------------------- /src/renderer/components/sidebar/TheSidebar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 45 | 46 | 62 | -------------------------------------------------------------------------------- /.electron-vue/dev-client.js: -------------------------------------------------------------------------------- 1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 2 | 3 | hotClient.subscribe(event => { 4 | /** 5 | * Reload browser when HTMLWebpackPlugin emits a new index.html 6 | * 7 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved. 8 | * https://github.com/SimulatedGREG/electron-vue/issues/437 9 | * https://github.com/jantimon/html-webpack-plugin/issues/680 10 | */ 11 | // if (event.action === 'reload') { 12 | // window.location.reload() 13 | // } 14 | 15 | /** 16 | * Notify `mainWindow` when `main` process is compiling, 17 | * giving notice for an expected reload of the `electron` process 18 | */ 19 | if (event.action === 'compiling') { 20 | document.body.innerHTML += ` 21 | 34 | 35 |
36 | Compiling Main Process... 37 |
38 | ` 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /src/main/lib/analytics.js: -------------------------------------------------------------------------------- 1 | import ua from 'universal-analytics' 2 | import store from '../store' 3 | import { convertName } from '../util/platform' 4 | 5 | const pkg = require('../../../package.json') 6 | 7 | const analytics = ua('UA-56182454-13') 8 | const isDev = process.env.NODE_ENV === 'development' 9 | 10 | function track (path) { 11 | if (isDev) return 12 | 13 | const isAllowAnalytics = store.preferences.get('allowAnalytics') 14 | 15 | if (isAllowAnalytics) { 16 | const version = pkg.version 17 | analytics.pageview(`${version}/${path}`).send() 18 | } 19 | } 20 | 21 | function trackEvent (category, action, label, value) { 22 | if (isDev) return 23 | 24 | const isAllowAnalytics = store.preferences.get('allowAnalytics') 25 | const version = pkg.version 26 | 27 | if (isAllowAnalytics) { 28 | analytics.event(`${version}-${category}`, action, label, value).send() 29 | } 30 | } 31 | 32 | function initAnalytics () { 33 | if (isDev) return 34 | 35 | if (store.preferences.get('allowAnalytics') === undefined) { 36 | store.preferences.set('allowAnalytics', true) 37 | } 38 | 39 | const version = pkg.version 40 | const installedVersion = store.preferences.get('install') 41 | 42 | if (installedVersion !== version) { 43 | store.preferences.set('install', version) 44 | const os = convertName(process.platform) 45 | track(`${os}/install`) 46 | } 47 | } 48 | 49 | export { track, trackEvent, initAnalytics } 50 | -------------------------------------------------------------------------------- /src/renderer/assets/scss/base.scss: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: var(--font-primary); 3 | background-color: var(--color-bg); 4 | } 5 | 6 | #app { 7 | color: var(--color-text); 8 | font-size: var(--text-md); 9 | } 10 | 11 | h1, 12 | h2, 13 | h3, 14 | h4, 15 | h5, 16 | h6 { 17 | font-weight: 600; 18 | } 19 | h1 { 20 | font-size: var(--text-xxxl); 21 | margin: var(--spacing-xl) 0; 22 | } 23 | h2 { 24 | font-size: var(--text-xxl); 25 | margin: var(--spacing-xl) 0; 26 | } 27 | h3 { 28 | font-size: var(--text-xl); 29 | margin: var(--spacing-xl) 0; 30 | } 31 | h3 { 32 | font-size: var(--text-lg); 33 | margin: var(--spacing-lg) 0; 34 | } 35 | h4 { 36 | font-size: var(--text-md); 37 | margin: var(--spacing-md) 0; 38 | } 39 | h5 { 40 | font-size: var(--text-sm); 41 | margin: var(--spacing-xs) 0; 42 | } 43 | h6 { 44 | font-size: var(--text-xs); 45 | text-transform: uppercase; 46 | margin: var(--spacing-xs) 0; 47 | font-weight: 700; 48 | color: var(--color-contrast-medium); 49 | } 50 | 51 | .gutter-line { 52 | position: absolute; 53 | top: 0; 54 | right: 0; 55 | width: 1px; 56 | height: 100vh; 57 | background-color: var(--color-border); 58 | cursor: col-resize; 59 | &::after { 60 | content: ''; 61 | display: block; 62 | height: 100%; 63 | width: 8px; 64 | position: absolute; 65 | left: -3px; 66 | z-index: 10; 67 | } 68 | } 69 | 70 | .desc { 71 | font-size: var(--text-sm); 72 | color: var(--color-contrast-medium); 73 | margin-top: var(--spacing-xs); 74 | } -------------------------------------------------------------------------------- /src/renderer/lib/ipcRenderer.js: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron' 2 | import store from '@/store' 3 | import router from '@/router' 4 | import eventBus from '@/event-bus' 5 | import { track } from '@@/lib/analytics' 6 | 7 | ipcRenderer.on('menu:new-snippet', () => { 8 | const folderId = store.getters['folders/selectedId'] 9 | store.dispatch('snippets/addSnippet', { folderId }) 10 | track('snippets/new') 11 | }) 12 | 13 | ipcRenderer.on('menu:new-fragment', () => { 14 | const snippetId = store.getters['snippets/selectedId'] 15 | if (snippetId) { 16 | eventBus.$emit('snippet:new-fragment') 17 | } 18 | track('snippets/new-fragment') 19 | }) 20 | 21 | ipcRenderer.on('menu:new-folder', () => { 22 | store.dispatch('folders/addFolder') 23 | track('folders/new') 24 | }) 25 | 26 | ipcRenderer.on('menu:preferences', () => { 27 | router.push('/preferences') 28 | track('view/preferences') 29 | }) 30 | 31 | ipcRenderer.on('menu:find-snippets', () => { 32 | eventBus.$emit('menu:find-snippets') 33 | track('snippets/search') 34 | }) 35 | 36 | ipcRenderer.on('menu:copy-snippet', () => { 37 | eventBus.$emit('menu:copy-snippet') 38 | track('snippets/copied') 39 | }) 40 | 41 | ipcRenderer.on('menu:format-snippet', () => { 42 | eventBus.$emit('menu:format-snippet') 43 | }) 44 | 45 | ipcRenderer.on('menu:markdown-preview', (e, value) => { 46 | store.commit('app/SET_MARKDOWN_PREVIEW', value) 47 | }) 48 | 49 | ipcRenderer.on('menu:favorites', () => { 50 | store.dispatch('folders/setSelectedFolder', 'favorites') 51 | }) 52 | 53 | ipcRenderer.on('update-available', () => { 54 | store.commit('app/SET_UPDATE_AVAILABLE', true) 55 | }) 56 | -------------------------------------------------------------------------------- /src/main/main.js: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from 'electron' 2 | import store from './store' 3 | 4 | const isDev = process.env.NODE_ENV === 'development' 5 | 6 | let mainWindow 7 | const winURL = 8 | process.env.NODE_ENV === 'development' 9 | ? 'http://localhost:9080' 10 | : `file://${__dirname}/index.html` 11 | 12 | function createMainWindow () { 13 | const bounds = { 14 | height: 563, 15 | width: 1000, 16 | ...store.app.get('bounds') 17 | } 18 | 19 | const backgroundColor = 20 | store.preferences.get('theme') === 'dark' ? '#333' : '#fff' 21 | 22 | mainWindow = new BrowserWindow({ 23 | title: 'massCode', 24 | useContentSize: true, 25 | titleBarStyle: 'hidden', 26 | // Убираем хайлайт вокруг приложения на Mac 27 | transparent: process.platform === 'darwin', 28 | backgroundColor, 29 | webPreferences: { 30 | nodeIntegration: true 31 | } 32 | }) 33 | 34 | mainWindow.setBounds(bounds) 35 | mainWindow.loadURL(winURL) 36 | 37 | if (isDev) { 38 | mainWindow.webContents.openDevTools({ mode: 'detach' }) 39 | } 40 | 41 | if (process.platform === 'darwin') { 42 | mainWindow.on('close', e => { 43 | e.preventDefault() 44 | 45 | if (mainWindow.isFullScreen()) { 46 | mainWindow.once('leave-full-screen', () => { 47 | mainWindow.hide() 48 | }) 49 | mainWindow.setFullScreen(false) 50 | } else { 51 | mainWindow.hide() 52 | } 53 | store.app.set('bounds', mainWindow.getBounds()) 54 | }) 55 | } 56 | 57 | mainWindow.on('closed', e => { 58 | mainWindow = null 59 | }) 60 | } 61 | 62 | export { createMainWindow, mainWindow } 63 | -------------------------------------------------------------------------------- /static/css/hljs/atom-one-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Atom One Dark by Daniel Gamage 4 | Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax 5 | 6 | base: #282c34 7 | mono-1: #abb2bf 8 | mono-2: #818896 9 | mono-3: #5c6370 10 | hue-1: #56b6c2 11 | hue-2: #61aeee 12 | hue-3: #c678dd 13 | hue-4: #98c379 14 | hue-5: #e06c75 15 | hue-5-2: #be5046 16 | hue-6: #d19a66 17 | hue-6-2: #e6c07b 18 | 19 | */ 20 | 21 | .hljs { 22 | display: block; 23 | overflow-x: auto; 24 | padding: 0.5em; 25 | color: #abb2bf; 26 | background: #282c34; 27 | } 28 | 29 | .hljs-comment, 30 | .hljs-quote { 31 | color: #5c6370; 32 | font-style: italic; 33 | } 34 | 35 | .hljs-doctag, 36 | .hljs-keyword, 37 | .hljs-formula { 38 | color: #c678dd; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name, 43 | .hljs-selector-tag, 44 | .hljs-deletion, 45 | .hljs-subst { 46 | color: #e06c75; 47 | } 48 | 49 | .hljs-literal { 50 | color: #56b6c2; 51 | } 52 | 53 | .hljs-string, 54 | .hljs-regexp, 55 | .hljs-addition, 56 | .hljs-attribute, 57 | .hljs-meta-string { 58 | color: #98c379; 59 | } 60 | 61 | .hljs-built_in, 62 | .hljs-class .hljs-title { 63 | color: #e6c07b; 64 | } 65 | 66 | .hljs-attr, 67 | .hljs-variable, 68 | .hljs-template-variable, 69 | .hljs-type, 70 | .hljs-selector-class, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-number { 74 | color: #d19a66; 75 | } 76 | 77 | .hljs-symbol, 78 | .hljs-bullet, 79 | .hljs-link, 80 | .hljs-meta, 81 | .hljs-selector-id, 82 | .hljs-title { 83 | color: #61aeee; 84 | } 85 | 86 | .hljs-emphasis { 87 | font-style: italic; 88 | } 89 | 90 | .hljs-strong { 91 | font-weight: bold; 92 | } 93 | 94 | .hljs-link { 95 | text-decoration: underline; 96 | } 97 | -------------------------------------------------------------------------------- /static/css/hljs/atom-one-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Atom One Light by Daniel Gamage 4 | Original One Light Syntax theme from https://github.com/atom/one-light-syntax 5 | 6 | base: #fafafa 7 | mono-1: #383a42 8 | mono-2: #686b77 9 | mono-3: #a0a1a7 10 | hue-1: #0184bb 11 | hue-2: #4078f2 12 | hue-3: #a626a4 13 | hue-4: #50a14f 14 | hue-5: #e45649 15 | hue-5-2: #c91243 16 | hue-6: #986801 17 | hue-6-2: #c18401 18 | 19 | */ 20 | 21 | .hljs { 22 | display: block; 23 | overflow-x: auto; 24 | padding: 0.5em; 25 | color: #383a42; 26 | background: #fafafa; 27 | } 28 | 29 | .hljs-comment, 30 | .hljs-quote { 31 | color: #a0a1a7; 32 | font-style: italic; 33 | } 34 | 35 | .hljs-doctag, 36 | .hljs-keyword, 37 | .hljs-formula { 38 | color: #a626a4; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name, 43 | .hljs-selector-tag, 44 | .hljs-deletion, 45 | .hljs-subst { 46 | color: #e45649; 47 | } 48 | 49 | .hljs-literal { 50 | color: #0184bb; 51 | } 52 | 53 | .hljs-string, 54 | .hljs-regexp, 55 | .hljs-addition, 56 | .hljs-attribute, 57 | .hljs-meta-string { 58 | color: #50a14f; 59 | } 60 | 61 | .hljs-built_in, 62 | .hljs-class .hljs-title { 63 | color: #c18401; 64 | } 65 | 66 | .hljs-attr, 67 | .hljs-variable, 68 | .hljs-template-variable, 69 | .hljs-type, 70 | .hljs-selector-class, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-number { 74 | color: #986801; 75 | } 76 | 77 | .hljs-symbol, 78 | .hljs-bullet, 79 | .hljs-link, 80 | .hljs-meta, 81 | .hljs-selector-id, 82 | .hljs-title { 83 | color: #4078f2; 84 | } 85 | 86 | .hljs-emphasis { 87 | font-style: italic; 88 | } 89 | 90 | .hljs-strong { 91 | font-weight: bold; 92 | } 93 | 94 | .hljs-link { 95 | text-decoration: underline; 96 | } 97 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppSelect.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 52 | 53 | 81 | -------------------------------------------------------------------------------- /src/renderer/components/sidebar/TheTags.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 56 | 57 | 86 | -------------------------------------------------------------------------------- /src/renderer/store/modules/preferences.js: -------------------------------------------------------------------------------- 1 | import electronStore from '@@/store' 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | renderWhitespace: electronStore.preferences.get('renderWhitespace'), 7 | wordWrap: electronStore.preferences.get('wordWrap'), 8 | tabSize: electronStore.preferences.get('tabSize'), 9 | insertSpaces: electronStore.preferences.get('insertSpaces'), 10 | prettierSemi: electronStore.preferences.get('prettierSemi'), 11 | prettierQuotes: electronStore.preferences.get('prettierQuotes') 12 | }, 13 | getters: {}, 14 | mutations: { 15 | SET_RENDER_WHITESPACE (state, type) { 16 | state.renderWhitespace = type 17 | }, 18 | SET_WORD_WRAP (state, type) { 19 | state.wordWrap = type 20 | }, 21 | SET_TAB_SIZE (state, size) { 22 | state.tabSize = size 23 | }, 24 | SET_INSERT_SPACES (state, bool) { 25 | state.insertSpaces = bool 26 | }, 27 | SET_PRETTIER_SEMI (state, bool) { 28 | state.prettierSemi = bool 29 | }, 30 | SET_PRETTIER_QUOTES (state, bool) { 31 | state.prettierQuotes = bool 32 | } 33 | }, 34 | actions: { 35 | setWhitespaceType ({ commit }, type) { 36 | commit('SET_RENDER_WHITESPACE', type) 37 | electronStore.preferences.set('renderWhitespace', type) 38 | }, 39 | setWordWrap ({ commit }, type) { 40 | commit('SET_WORD_WRAP', type) 41 | electronStore.preferences.set('wordWrap', type) 42 | }, 43 | setTabSize ({ commit }, size) { 44 | commit('SET_TAB_SIZE', Number(size)) 45 | electronStore.preferences.set('tabSize', Number(size)) 46 | }, 47 | setInsertSpaces ({ commit }, bool) { 48 | commit('SET_INSERT_SPACES', bool) 49 | electronStore.preferences.set('insertSpaces', bool) 50 | }, 51 | setPrettierSemi ({ commit }, bool) { 52 | commit('SET_PRETTIER_SEMI', bool) 53 | electronStore.preferences.set('prettierSemi', bool) 54 | }, 55 | setPrettierQuotes ({ commit }, bool) { 56 | commit('SET_PRETTIER_QUOTES', bool) 57 | electronStore.preferences.set('prettierQuotes', bool) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppAlert.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 59 | 60 | 91 | -------------------------------------------------------------------------------- /src/renderer/components/preferences/Assistant.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'main' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const TerserPlugin = require('terser-webpack-plugin') 10 | 11 | let mainConfig = { 12 | entry: { 13 | main: path.join(__dirname, '../src/main/index.js') 14 | }, 15 | externals: [...Object.keys(dependencies || {})], 16 | module: { 17 | rules: [ 18 | // { 19 | // test: /\.(js)$/, 20 | // enforce: 'pre', 21 | // exclude: /node_modules/, 22 | // use: { 23 | // loader: 'eslint-loader', 24 | // options: { 25 | // formatter: require('eslint-friendly-formatter') 26 | // } 27 | // } 28 | // }, 29 | { 30 | test: /\.js$/, 31 | use: 'babel-loader', 32 | exclude: /node_modules/ 33 | }, 34 | { 35 | test: /\.node$/, 36 | use: 'node-loader' 37 | } 38 | ] 39 | }, 40 | node: { 41 | __dirname: process.env.NODE_ENV !== 'production', 42 | __filename: process.env.NODE_ENV !== 'production' 43 | }, 44 | output: { 45 | filename: '[name].js', 46 | libraryTarget: 'commonjs2', 47 | path: path.join(__dirname, '../dist/electron') 48 | }, 49 | plugins: [new webpack.NoEmitOnErrorsPlugin()], 50 | optimization: { 51 | minimizer: [] 52 | }, 53 | resolve: { 54 | extensions: ['.js', '.json', '.node'] 55 | }, 56 | target: 'electron-main' 57 | } 58 | 59 | /** 60 | * Adjust mainConfig for development settings 61 | */ 62 | if (process.env.NODE_ENV !== 'production') { 63 | mainConfig.plugins.push( 64 | new webpack.DefinePlugin({ 65 | __static: `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 66 | }) 67 | ) 68 | } 69 | 70 | /** 71 | * Adjust mainConfig for production settings 72 | */ 73 | if (process.env.NODE_ENV === 'production') { 74 | mainConfig.plugins.push( 75 | new webpack.DefinePlugin({ 76 | 'process.env.NODE_ENV': '"production"' 77 | }) 78 | ) 79 | const terserOptions = require('./terserOptions') 80 | mainConfig.optimization.minimizer.push(new TerserPlugin(terserOptions())) 81 | } 82 | 83 | module.exports = mainConfig 84 | -------------------------------------------------------------------------------- /src/main/tray.js: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, Tray, ipcMain, globalShortcut } from 'electron' 2 | import store from './store' 3 | 4 | import path from 'path' 5 | 6 | let tray 7 | let trayWindow 8 | 9 | const winURL = 10 | process.env.NODE_ENV === 'development' 11 | ? 'http://localhost:9080/#tray' 12 | : `file://${__dirname}/index.html#tray` 13 | 14 | function createTray () { 15 | tray = new Tray(path.join(__static, '/trayTemplate.png')) 16 | 17 | tray.on('click', () => { 18 | showTray() 19 | }) 20 | createTrayWindow() 21 | 22 | const shortcut = store.preferences.get('assistantShortcut') 23 | 24 | globalShortcut.register(shortcut, () => { 25 | showTray() 26 | }) 27 | 28 | ipcMain.on('preferences:assistant:shortcut', (e, shortcuts) => { 29 | globalShortcut.unregister(shortcuts.old) 30 | globalShortcut.register(shortcuts.new, () => { 31 | showTray() 32 | }) 33 | }) 34 | } 35 | 36 | function createTrayWindow () { 37 | trayWindow = new BrowserWindow({ 38 | width: 300, 39 | height: 400, 40 | frame: false, 41 | fullscreenable: false, 42 | resizable: false, 43 | transparent: true, 44 | alwaysOnTop: true, 45 | show: false, 46 | movable: false, 47 | webPreferences: { 48 | nodeIntegration: true, 49 | devTools: false 50 | } 51 | }) 52 | 53 | trayWindow.loadURL(winURL) 54 | 55 | trayWindow.on('blur', () => { 56 | trayWindow.hide() 57 | trayWindow.webContents.send('tray:hide') 58 | }) 59 | 60 | trayWindow.on('show', () => { 61 | trayWindow.focus() 62 | trayWindow.webContents.send('tray:show') 63 | }) 64 | 65 | ipcMain.on('tray:hide', () => { 66 | trayWindow.hide() 67 | }) 68 | } 69 | 70 | function showTray () { 71 | const trayWindowBounds = trayWindow.getBounds() 72 | 73 | let { x, y, width, height } = tray.getBounds() 74 | x = x - (trayWindowBounds.width / 2 - width / 2) 75 | y = y + height + 3 76 | 77 | trayWindow.setBounds({ x, y }) 78 | trayWindow.show() 79 | } 80 | 81 | function destroyTray () { 82 | const shortcut = store.preferences.get('assistantShortcut') 83 | 84 | trayWindow.destroy() 85 | tray.destroy() 86 | globalShortcut.unregister(shortcut) 87 | } 88 | 89 | export { createTray, createTrayWindow, showTray, destroyTray } 90 | -------------------------------------------------------------------------------- /src/renderer/views/Main.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 71 | 72 | 93 | -------------------------------------------------------------------------------- /src/renderer/assets/scss/variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --sidebar-width: 180px; 3 | --snippets-list-width: 220px; 4 | --snippets-view-header-height: 60px; 5 | --snippets-view-header-full-height: calc(var(--snippets-view-header-height) + var(--snippets-tags-height)); 6 | --snippets-view-footer-height: 30px; 7 | --snippet-tab-header-height: 40px; 8 | --snippet-tab-height: 30px; 9 | --title-bar-height: 20px; 10 | --menu-header: 80px; 11 | 12 | --spacing-unit: 4px; 13 | --spacing-xs: calc(var(--spacing-unit) * 2); 14 | --spacing-sm: calc(var(--spacing-unit) * 4); 15 | --spacing-md: calc(var(--spacing-unit) * 6); 16 | --spacing-lg: calc(var(--spacing-unit) * 8); 17 | --spacing-xl: calc(var(--spacing-unit) * 10); 18 | 19 | --font-primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 20 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 21 | --font-code: Menlo, Monaco, "Courier New", monospace; 22 | 23 | --text-base-size: 10px; 24 | --text-xs: calc(var(--text-base-size) * 1); 25 | --text-sm: calc(var(--text-base-size) * 1.2); 26 | --text-md: calc(var(--text-base-size) * 1.4); 27 | --text-lg: calc(var(--text-base-size) * 1.8); 28 | --text-xl: calc(var(--text-base-size) * 2.4); 29 | --text-xxl: calc(var(--text-base-size) * 3.2); 30 | --text-xxxl: calc(var(--text-base-size) * 3.6); 31 | } 32 | 33 | [data-theme='light'] { 34 | --color-primary: hsl(213, 81%, 67%); 35 | 36 | --color-bg: hsl(0, 0%, 100%); 37 | --color-contrast-lower: hsl(0, 0%, 97%); 38 | --color-contrast-low: hsl(0, 0%, 85%); 39 | --color-contrast-low-alt: hsl(0, 0%, 80%); 40 | --color-contrast-medium: hsl(0, 0%, 50%); 41 | --color-contrast-high: hsl(0, 0%, 15%); 42 | --color-contrast-higher: hsl(0, 0%, 0%); 43 | 44 | --color-text: var(--color-contrast-high); 45 | --color-border: var(--color-contrast-low); 46 | } 47 | 48 | [data-theme='dark'] { 49 | --color-primary: hsl(213, 81%, 67%); 50 | 51 | --color-bg: hsl(0, 0%, 20%); 52 | --color-contrast-lower: hsl(0, 0%, 25%); 53 | --color-contrast-low: hsl(0, 0%, 32%); 54 | --color-contrast-low-alt: hsl(0, 0%, 37%); 55 | --color-contrast-medium: hsl(0, 0%, 50%); 56 | --color-contrast-high: hsl(0, 0%, 85%); 57 | --color-contrast-higher: hsl(0, 0%, 100%); 58 | 59 | --color-text: var(--color-contrast-high); 60 | --color-border: var(--color-contrast-low); 61 | } 62 | -------------------------------------------------------------------------------- /src/renderer/components/sidebar/SidebarList.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 59 | 60 | 104 | -------------------------------------------------------------------------------- /src/renderer/components/uikit/AppMenu.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 78 | 79 | 104 | -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | import { app, Menu, ipcMain, dialog, BrowserWindow } from 'electron' 2 | import { createMainWindow, mainWindow } from './main' 3 | import { createTray, destroyTray } from './tray' 4 | import store from './store' 5 | import mainMenu from './lib/main-menu' 6 | import touchBar from './lib/touch-bar' 7 | import { initAnalytics } from './lib/analytics' 8 | import { checkForUpdatesAndNotify } from './lib/update-check' 9 | 10 | const isDev = process.env.NODE_ENV === 'development' 11 | let menu 12 | 13 | /** 14 | * Set `__static` path to static files in production 15 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 16 | */ 17 | if (!isDev) { 18 | global.__static = require('path') 19 | .join(__dirname, '/static') 20 | .replace(/\\/g, '\\\\') 21 | } 22 | 23 | function init () { 24 | createMainWindow() 25 | 26 | menu = Menu.buildFromTemplate(mainMenu(mainWindow)) 27 | Menu.setApplicationMenu(menu) 28 | } 29 | 30 | function initTray () { 31 | if (process.platform !== 'darwin') return 32 | 33 | const isAssistant = store.preferences.get('assistant') 34 | if (isAssistant) createTray() 35 | } 36 | 37 | app.on('ready', () => { 38 | init() 39 | initTray() 40 | initAnalytics() 41 | checkForUpdatesAndNotify() 42 | mainWindow.setTouchBar(touchBar) 43 | }) 44 | 45 | app.on('window-all-closed', () => { 46 | if (process.platform !== 'darwin') { 47 | app.quit() 48 | } 49 | }) 50 | 51 | app.on('before-quit', () => { 52 | mainWindow.removeAllListeners() 53 | }) 54 | 55 | app.on('activate', () => { 56 | if (mainWindow === null) { 57 | init() 58 | } else { 59 | mainWindow.show() 60 | } 61 | }) 62 | 63 | ipcMain.on('preferences:assistant', (e, enable) => { 64 | enable ? initTray() : destroyTray() 65 | }) 66 | 67 | ipcMain.on('message', (e, options) => { 68 | dialog.showMessageBox(BrowserWindow.getFocusedWindow(), options) 69 | }) 70 | 71 | // Переключение чекбокса у Editor/Preview Markdown 72 | ipcMain.on('menu:markdown-preview', (e, value) => { 73 | menu.items[3].submenu.items[2].checked = value 74 | }) 75 | 76 | /** 77 | * Auto Updater 78 | * 79 | * Uncomment the following code below and install `electron-updater` to 80 | * support auto updating. Code Signing with a valid certificate is required. 81 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating 82 | */ 83 | 84 | /* 85 | import { autoUpdater } from 'electron-updater' 86 | 87 | autoUpdater.on('update-downloaded', () => { 88 | autoUpdater.quitAndInstall() 89 | }) 90 | 91 | app.on('ready', () => { 92 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() 93 | }) 94 | */ 95 | -------------------------------------------------------------------------------- /src/renderer/components/snippets/SnippetTabs.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 83 | 84 | 122 | -------------------------------------------------------------------------------- /src/renderer/components/snippets/ActionBar.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 81 | 82 | 110 | -------------------------------------------------------------------------------- /src/renderer/components/sidebar/TheLibrary.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 95 | 96 | 112 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 99 | 100 | 113 | -------------------------------------------------------------------------------- /src/renderer/components/tray/TraySnippetListItem.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 83 | 84 | 131 | -------------------------------------------------------------------------------- /src/renderer/assets/scss/reset.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | line-height: 1.15; 7 | -webkit-text-size-adjust: 100%; 8 | } 9 | 10 | body { 11 | margin: 0; 12 | } 13 | 14 | h1 { 15 | font-size: 2em; 16 | margin: 0.67em 0; 17 | } 18 | 19 | hr { 20 | box-sizing: content-box; 21 | height: 0; 22 | overflow: visible; 23 | } 24 | 25 | a { 26 | background-color: transparent; 27 | } 28 | 29 | abbr[title] { 30 | border-bottom: none; 31 | text-decoration: underline dotted; 32 | } 33 | 34 | b, 35 | strong { 36 | font-weight: bolder; 37 | } 38 | 39 | small { 40 | font-size: 80%; 41 | } 42 | 43 | sub, 44 | sup { 45 | font-size: 75%; 46 | line-height: 0; 47 | position: relative; 48 | vertical-align: baseline; 49 | } 50 | 51 | sub { 52 | bottom: -0.25em; 53 | } 54 | 55 | sup { 56 | top: -0.5em; 57 | } 58 | 59 | img { 60 | border-style: none; 61 | } 62 | 63 | button, 64 | input, 65 | optgroup, 66 | select, 67 | textarea { 68 | font-family: inherit; 69 | font-size: 100%; 70 | line-height: 1.15; 71 | margin: 0; 72 | } 73 | 74 | button, 75 | input { 76 | overflow: visible; 77 | } 78 | 79 | button, 80 | select { 81 | text-transform: none; 82 | } 83 | 84 | button, 85 | [type='button'], 86 | [type='reset'], 87 | [type='submit'] { 88 | -webkit-appearance: button; 89 | } 90 | 91 | button::-moz-focus-inner, 92 | [type='button']::-moz-focus-inner, 93 | [type='reset']::-moz-focus-inner, 94 | [type='submit']::-moz-focus-inner { 95 | border-style: none; 96 | padding: 0; 97 | } 98 | 99 | button:-moz-focusring, 100 | [type='button']:-moz-focusring, 101 | [type='reset']:-moz-focusring, 102 | [type='submit']:-moz-focusring { 103 | outline: 1px dotted ButtonText; 104 | } 105 | 106 | fieldset { 107 | padding: 0.35em 0.75em 0.625em; 108 | } 109 | 110 | legend { 111 | box-sizing: border-box; 112 | color: inherit; 113 | display: table; 114 | max-width: 100%; 115 | white-space: normal; 116 | padding: 0; 117 | } 118 | 119 | progress { 120 | vertical-align: baseline; 121 | } 122 | 123 | textarea { 124 | overflow: auto; 125 | } 126 | 127 | [type='checkbox'], 128 | [type='radio'] { 129 | box-sizing: border-box; 130 | padding: 0; 131 | } 132 | 133 | [type='number']::-webkit-inner-spin-button, 134 | [type='number']::-webkit-outer-spin-button { 135 | height: auto; 136 | } 137 | 138 | [type='search'] { 139 | -webkit-appearance: textfield; 140 | outline-offset: -2px; 141 | } 142 | 143 | [type='search']::-webkit-search-decoration { 144 | -webkit-appearance: none; 145 | } 146 | 147 | ::-webkit-file-upload-button { 148 | -webkit-appearance: button; 149 | font: inherit; 150 | } 151 | 152 | summary { 153 | display: list-item; 154 | } 155 | 156 | main, 157 | details { 158 | display: block; 159 | } 160 | 161 | pre, 162 | code, 163 | kbd, 164 | samp { 165 | font-family: monospace, monospace; 166 | font-size: 1em; 167 | } 168 | 169 | template, 170 | [hidden] { 171 | display: none; 172 | } 173 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 79 | 80 | 81 | 82 | 83 |
84 |
85 |

massCode is loading

86 |
87 |
88 |
89 |
90 |
91 |
92 | 93 | 94 | <% if (!htmlWebpackPlugin.options.isBrowser && !htmlWebpackPlugin.options.isDevelopment) { %> 95 | 98 | <% } %> 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /.github/COMMIT_CONVENTION.md: -------------------------------------------------------------------------------- 1 | ## Git Commit Message Convention 2 | 3 | > This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular). 4 | 5 | #### TL;DR: 6 | 7 | Messages must be matched by the following regex: 8 | 9 | ```js 10 | /^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\(.+\))?: .{1,50}/ 11 | ``` 12 | 13 | #### Examples 14 | 15 | Appears under "Features" header, `compiler` subheader: 16 | 17 | ``` 18 | feat(compiler): add 'comments' option 19 | ``` 20 | 21 | Appears under "Bug Fixes" header, `v-model` subheader, with a link to issue #28: 22 | 23 | ``` 24 | fix(v-model): handle events on blur 25 | 26 | close #28 27 | ``` 28 | 29 | Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation: 30 | 31 | ``` 32 | perf(core): improve vdom diffing by removing 'foo' option 33 | 34 | BREAKING CHANGE: The 'foo' option has been removed. 35 | ``` 36 | 37 | The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header. 38 | 39 | ``` 40 | revert: feat(compiler): add 'comments' option 41 | 42 | This reverts commit 667ecc1654a317a13331b17617d973392f415f02. 43 | ``` 44 | 45 | ### Full Message Format 46 | 47 | A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**: 48 | 49 | ``` 50 | (): 51 | 52 | 53 | 54 |