mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd3d1607a4 | ||
|
|
abbc0fbcf1 | ||
|
|
499c3f2e13 | ||
|
|
627845f6e4 | ||
|
|
07eea76057 | ||
|
|
fecc4e9b79 | ||
|
|
58e9302f15 | ||
|
|
86f649fab1 | ||
|
|
9ac0becfb2 | ||
|
|
f75e872415 | ||
|
|
aef0712165 | ||
|
|
bed4b7fd27 | ||
|
|
b53ff5daf3 | ||
|
|
bb0872b4fc | ||
|
|
b65101f4be | ||
|
|
593d242a4c | ||
|
|
db7f339c34 | ||
|
|
9f3575a874 | ||
|
|
1c9c59c512 | ||
|
|
127202b831 | ||
|
|
4f8a04ed21 | ||
|
|
63b2e0560b | ||
|
|
6907cf9972 | ||
|
|
d4f8d1498d | ||
|
|
0952e4a664 | ||
|
|
983bfb7adf | ||
|
|
d7aaf5e210 | ||
|
|
50281132ad | ||
|
|
6a2b22015e | ||
|
|
0fe83a0583 | ||
|
|
ce74e69480 | ||
|
|
ddea2aeb22 | ||
|
|
7bbe69cce9 | ||
|
|
e921e30d64 | ||
|
|
cd4f9d8bb4 | ||
|
|
a0553788b6 | ||
|
|
1a183d78af | ||
|
|
cabcaa892c | ||
|
|
01c9d62a2b | ||
|
|
ba76df863c | ||
|
|
81441a0895 | ||
|
|
da0222f213 | ||
|
|
fb8a2eb2e0 | ||
|
|
cde2e27e04 | ||
|
|
3758ea2cf4 | ||
|
|
e62fc11328 | ||
|
|
3cbfae83c1 | ||
|
|
57667654ef | ||
|
|
eadd66fa91 | ||
|
|
75cd94a39a | ||
|
|
7872bfe19d | ||
|
|
af008e69c2 | ||
|
|
a549abc20f | ||
|
|
116344737a | ||
|
|
93c03f4e88 | ||
|
|
445332c27c | ||
|
|
c42e1892d0 | ||
|
|
b6b526dd57 | ||
|
|
3ef7f19ffc | ||
|
|
9d0d851c2e |
18
LICENSE
18
LICENSE
@@ -1,2 +1,16 @@
|
||||
本製品をインストール、または使用することによって、お客様は利用規約(
|
||||
https://b00st.io/regulations.html)より拘束されることに承諾されたものとします。利用規約に同意されない場合、Boostnoteは、お客様に本製品のインストール、使用のいずれも許諾できません。
|
||||
Boostnote - the simplest note app
|
||||
|
||||
Copyright (C) 2016 MAISIN&CO.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
@@ -21,6 +21,76 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
this.configApplyHandler = (e, config) => this.handleConfigApply(e, config)
|
||||
this.changeHandler = e => this.handleChange(e)
|
||||
this.blurHandler = (e) => {
|
||||
if (e.relatedTarget === null) {
|
||||
return
|
||||
}
|
||||
|
||||
let isFocusingToSearch = e.relatedTarget.className && e.relatedTarget.className.split(' ').some(clss => {
|
||||
return clss === 'ace_search_field' || clss === 'ace_searchbtn' || clss === 'ace_replacebtn' || clss === 'ace_searchbtn_close' || clss === 'ace_text-input'
|
||||
})
|
||||
if (isFocusingToSearch) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.props.onBlur) this.props.onBlur(e)
|
||||
}
|
||||
|
||||
this.killedBuffer = ''
|
||||
this.execHandler = (e) => {
|
||||
console.log(e.command.name)
|
||||
switch (e.command.name) {
|
||||
case 'gotolinestart':
|
||||
e.preventDefault()
|
||||
{
|
||||
let position = this.editor.getCursorPosition()
|
||||
this.editor.navigateTo(position.row, 0)
|
||||
}
|
||||
break
|
||||
case 'gotolineend':
|
||||
e.preventDefault()
|
||||
let position = this.editor.getCursorPosition()
|
||||
this.editor.navigateTo(position.row, this.editor.getSession().getLine(position.row).length)
|
||||
break
|
||||
case 'jumptomatching':
|
||||
e.preventDefault()
|
||||
this.editor.navigateUp()
|
||||
break
|
||||
case 'removetolineend':
|
||||
e.preventDefault()
|
||||
let range = this.editor.getSelectionRange()
|
||||
let session = this.editor.getSession()
|
||||
if (range.isEmpty()) {
|
||||
range.setEnd(range.start.row, session.getLine(range.start.row).length)
|
||||
this.killedBuffer = session.getTextRange(range)
|
||||
if (this.killedBuffer.length > 0) {
|
||||
console.log('remove to lineend')
|
||||
session.remove(range)
|
||||
} else {
|
||||
if (session.getLength() === range.start.row) {
|
||||
return
|
||||
}
|
||||
range.setStart(range.start.row, range.end.col)
|
||||
range.setEnd(range.start.row + 1, 0)
|
||||
this.killedBuffer = '\n'
|
||||
session.remove(range)
|
||||
}
|
||||
} else {
|
||||
this.killedBuffer = session.getTextRange(range)
|
||||
session.remove(range)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.afterExecHandler = (e) => {
|
||||
switch (e.command.name) {
|
||||
case 'find':
|
||||
Array.prototype.forEach.call(ReactDOM.findDOMNode(this).querySelectorAll('.ace_search_field, .ace_searchbtn, .ace_replacebtn, .ace_searchbtn_close'), el => {
|
||||
el.removeEventListener('blur', this.blurHandler)
|
||||
el.addEventListener('blur', this.blurHandler)
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
fontSize: config['editor-font-size'],
|
||||
@@ -49,6 +119,8 @@ export default class CodeEditor extends React.Component {
|
||||
editor.setReadOnly(!!this.props.readOnly)
|
||||
editor.setFontSize(this.state.fontSize)
|
||||
|
||||
editor.on('blur', this.blurHandler)
|
||||
|
||||
editor.commands.addCommand({
|
||||
name: 'Emacs cursor up',
|
||||
bindKey: {mac: 'Ctrl-P'},
|
||||
@@ -58,18 +130,29 @@ export default class CodeEditor extends React.Component {
|
||||
},
|
||||
readOnly: true
|
||||
})
|
||||
editor.commands.addCommand({
|
||||
name: 'Emacs cursor up',
|
||||
bindKey: {mac: 'Ctrl-Y'},
|
||||
exec: function (editor) {
|
||||
editor.insert(this.killedBuffer)
|
||||
}.bind(this),
|
||||
readOnly: true
|
||||
})
|
||||
editor.commands.addCommand({
|
||||
name: 'Focus title',
|
||||
bindKey: {win: 'Esc', mac: 'Esc'},
|
||||
exec: function (editor, e) {
|
||||
remote.getCurrentWebContents().send('list-focus')
|
||||
let currentWindow = remote.getCurrentWebContents()
|
||||
if (config['switch-preview'] === 'rightclick') {
|
||||
currentWindow.send('detail-preview')
|
||||
}
|
||||
currentWindow.send('list-focus')
|
||||
},
|
||||
readOnly: true
|
||||
})
|
||||
|
||||
editor.on('blur', () => {
|
||||
if (this.props.onBlur) this.props.onBlur()
|
||||
})
|
||||
editor.commands.on('exec', this.execHandler)
|
||||
editor.commands.on('afterExec', this.afterExecHandler)
|
||||
|
||||
var session = editor.getSession()
|
||||
let mode = _.findWhere(modes, {name: article.mode})
|
||||
@@ -92,6 +175,9 @@ export default class CodeEditor extends React.Component {
|
||||
componentWillUnmount () {
|
||||
ipc.removeListener('config-apply', this.configApplyHandler)
|
||||
this.editor.getSession().removeListener('change', this.changeHandler)
|
||||
this.editor.removeListener('blur', this.blurHandler)
|
||||
this.editor.commands.removeListener('exec', this.execHandler)
|
||||
this.editor.commands.removeListener('afterExec', this.afterExecHandler)
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
@@ -166,8 +252,8 @@ CodeEditor.propTypes = {
|
||||
key: PropTypes.string
|
||||
}),
|
||||
className: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
readOnly: PropTypes.bool
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@ const ipc = electron.ipcRenderer
|
||||
|
||||
const katex = window.katex
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
const sanitizeOpts = {
|
||||
allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
||||
'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'img', 'span', 'cite', 'del', 'u', 'sub', 'sup' ],
|
||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'img', 'span', 'cite', 'del', 'u', 'sub', 'sup', 's', 'input', 'label' ],
|
||||
allowedClasses: {
|
||||
'a': ['lineAnchor'],
|
||||
'div': ['math'],
|
||||
@@ -24,14 +26,20 @@ const sanitizeOpts = {
|
||||
allowedAttributes: {
|
||||
a: ['href', 'data-key'],
|
||||
img: [ 'src' ],
|
||||
label: ['for'],
|
||||
input: ['checked', 'type'],
|
||||
'*': ['id', 'name']
|
||||
},
|
||||
transformTags: {
|
||||
'*': function (tagName, attribs) {
|
||||
let href = attribs.href
|
||||
if (tagName === 'input' && attribs.type !== 'checkbox') {
|
||||
return false
|
||||
}
|
||||
if (_.isString(href) && href.match(/^#.+$/)) attribs.href = href.replace(/^#/, '#md-anchor-')
|
||||
if (attribs.id) attribs.id = 'md-anchor-' + attribs.id
|
||||
if (attribs.name) attribs.name = 'md-anchor-' + attribs.name
|
||||
if (attribs.for) attribs.for = 'md-anchor-' + attribs.for
|
||||
return {
|
||||
tagName: tagName,
|
||||
attribs: attribs
|
||||
@@ -41,12 +49,15 @@ const sanitizeOpts = {
|
||||
}
|
||||
|
||||
function handleAnchorClick (e) {
|
||||
if (e.target.attributes.href && e.target.attributes.href.nodeValue.match(/#.+/)) {
|
||||
if (this.attributes.href && this.attributes.href.nodeValue.match(/^#.+/)) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
shell.openExternal(e.target.href)
|
||||
let href = this.href
|
||||
if (href && href.match(/^http:\/\/|https:\/\/|mailto:\/\//)) {
|
||||
shell.openExternal(href)
|
||||
}
|
||||
}
|
||||
|
||||
function stopPropagation (e) {
|
||||
@@ -57,7 +68,7 @@ function stopPropagation (e) {
|
||||
function math2Katex (display) {
|
||||
return function (el) {
|
||||
try {
|
||||
katex.render(el.innerHTML, el, {display: display})
|
||||
katex.render(el.innerHTML.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/&/g, '&'), el, {display: display})
|
||||
el.className = 'math-rendered'
|
||||
} catch (e) {
|
||||
el.innerHTML = e.message
|
||||
@@ -111,22 +122,30 @@ export default class MarkdownPreview extends React.Component {
|
||||
|
||||
addListener () {
|
||||
var anchors = ReactDOM.findDOMNode(this).querySelectorAll('a:not(.lineAnchor)')
|
||||
var inputs = ReactDOM.findDOMNode(this).querySelectorAll('input')
|
||||
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].addEventListener('click', handleAnchorClick)
|
||||
anchors[i].addEventListener('mousedown', stopPropagation)
|
||||
anchors[i].addEventListener('mouseup', stopPropagation)
|
||||
}
|
||||
Array.prototype.forEach.call(anchors, anchor => {
|
||||
anchor.addEventListener('click', handleAnchorClick)
|
||||
anchor.addEventListener('mousedown', stopPropagation)
|
||||
anchor.addEventListener('mouseup', stopPropagation)
|
||||
})
|
||||
Array.prototype.forEach.call(inputs, input => {
|
||||
input.addEventListener('click', stopPropagation)
|
||||
})
|
||||
}
|
||||
|
||||
removeListener () {
|
||||
var anchors = ReactDOM.findDOMNode(this).querySelectorAll('a:not(.lineAnchor)')
|
||||
var inputs = ReactDOM.findDOMNode(this).querySelectorAll('input')
|
||||
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].removeEventListener('click', handleAnchorClick)
|
||||
anchors[i].removeEventListener('mousedown', stopPropagation)
|
||||
anchors[i].removeEventListener('mouseup', stopPropagation)
|
||||
}
|
||||
Array.prototype.forEach.call(anchors, anchor => {
|
||||
anchor.removeEventListener('click', handleAnchorClick)
|
||||
anchor.removeEventListener('mousedown', stopPropagation)
|
||||
anchor.removeEventListener('mouseup', stopPropagation)
|
||||
})
|
||||
Array.prototype.forEach.call(inputs, input => {
|
||||
input.removeEventListener('click', stopPropagation)
|
||||
})
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
@@ -185,7 +204,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
dangerouslySetInnerHTML={{__html: ' ' + content}}
|
||||
style={{
|
||||
fontSize: this.state.fontSize,
|
||||
fontFamily: this.state.fontFamily.trim() + ', helvetica, arial, sans-serif'
|
||||
fontFamily: this.state.fontFamily.trim() + (OSX ? '' : ', meiryo, \'Microsoft YaHei\'') + ', helvetica, arial, sans-serif'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -11,9 +11,17 @@ import _ from 'lodash'
|
||||
import dataStore from 'browser/lib/dataStore'
|
||||
|
||||
const electron = require('electron')
|
||||
const { clipboard, ipcRenderer } = electron
|
||||
const { clipboard, ipcRenderer, remote } = electron
|
||||
const path = require('path')
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.addEventListener('keydown', function (e) {
|
||||
if (e.keyCode === 73 && e.metaKey && e.altKey) {
|
||||
remote.getCurrentWindow().toggleDevTools()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function hideFinder () {
|
||||
ipcRenderer.send('hide-finder')
|
||||
}
|
||||
@@ -21,7 +29,6 @@ function hideFinder () {
|
||||
function notify (title, options) {
|
||||
if (process.platform === 'win32') {
|
||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||
options.silent = false
|
||||
}
|
||||
return new window.Notification(title, options)
|
||||
}
|
||||
@@ -88,7 +95,8 @@ class FinderMain extends React.Component {
|
||||
|
||||
ipcRenderer.send('copy-finder')
|
||||
notify('Saved to Clipboard!', {
|
||||
body: 'Paste it wherever you want!'
|
||||
body: 'Paste it wherever you want!',
|
||||
silent: true
|
||||
})
|
||||
hideFinder()
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ Post all records(except today)
|
||||
and remove all posted records
|
||||
*/
|
||||
export function postRecords (data) {
|
||||
if (process.NODE_ENV !== 'production') {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('post failed - NOT PRODUCTION ')
|
||||
return
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export function emit (type, data = {}) {
|
||||
}
|
||||
|
||||
// Count ARTICLE_CREATE and ARTICLE_UPDATE again by syntax
|
||||
if ((type === 'ARTICLE_CREATE' || type === 'ARTICLE_UPDATE') && data.mode != null) {
|
||||
if (type === 'ARTICLE_UPDATE' && data.mode != null) {
|
||||
let recordKey = type + '_BY_SYNTAX'
|
||||
if (todayRecord[recordKey] == null) todayRecord[recordKey] = {}
|
||||
|
||||
|
||||
@@ -5,6 +5,17 @@ const jetpack = require('fs-jetpack')
|
||||
const userDataPath = remote.app.getPath('userData')
|
||||
const configFile = 'config.json'
|
||||
|
||||
export default function fetchConfig () {
|
||||
return Object.assign({}, JSON.parse(jetpack.cwd(userDataPath).read(configFile, 'utf-8')))
|
||||
const defaultConfig = {
|
||||
'editor-font-size': '14',
|
||||
'editor-font-family': 'Monaco, Consolas',
|
||||
'editor-indent-type': 'space',
|
||||
'editor-indent-size': '4',
|
||||
'preview-font-size': '14',
|
||||
'preview-font-family': 'Lato',
|
||||
'switch-preview': 'blur',
|
||||
'disable-direct-write': false
|
||||
}
|
||||
|
||||
export default function fetchConfig () {
|
||||
return Object.assign({}, defaultConfig, JSON.parse(jetpack.cwd(userDataPath).read(configFile, 'utf-8')))
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@ var md = markdownit({
|
||||
return hljs.highlight(lang, str).value
|
||||
} catch (e) {}
|
||||
}
|
||||
return str
|
||||
return str.replace(/\&/g, '&').replace(/\</g, '<').replace(/\>/g, '>').replace(/\"/g, '"')
|
||||
}
|
||||
})
|
||||
md.use(emoji)
|
||||
md.use(emoji, {
|
||||
shortcuts: {}
|
||||
})
|
||||
md.use(math, {
|
||||
inlineRenderer: function (str) {
|
||||
return `<span class='math'>${str}</span>`
|
||||
@@ -26,6 +28,7 @@ md.use(math, {
|
||||
return `<div class='math'>${str}</div>`
|
||||
}
|
||||
})
|
||||
md.use(require('markdown-it-checkbox'))
|
||||
|
||||
let originalRenderToken = md.renderer.renderToken
|
||||
md.renderer.renderToken = function renderToken (tokens, idx, options) {
|
||||
|
||||
@@ -2,21 +2,43 @@ import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||
import CodeEditor from 'browser/components/CodeEditor'
|
||||
import activityRecord from 'browser/lib/activityRecord'
|
||||
import fetchConfig from 'browser/lib/fetchConfig'
|
||||
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
export const PREVIEW_MODE = 'PREVIEW_MODE'
|
||||
export const EDIT_MODE = 'EDIT_MODE'
|
||||
|
||||
let config = fetchConfig()
|
||||
ipc.on('config-apply', function (e, newConfig) {
|
||||
config = newConfig
|
||||
})
|
||||
|
||||
export default class ArticleEditor extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.configApplyHandler = (e, config) => this.handleConfigApply(e, config)
|
||||
this.isMouseDown = false
|
||||
this.state = {
|
||||
status: PREVIEW_MODE,
|
||||
cursorPosition: null,
|
||||
firstVisibleRow: null
|
||||
firstVisibleRow: null,
|
||||
switchPreview: config['switch-preview'],
|
||||
isTemporary: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
ipc.on('config-apply', this.configApplyHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ipc.removeListener('config-apply', this.configApplyHandler)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.article.key !== this.props.article.key) {
|
||||
this.setState({
|
||||
@@ -25,6 +47,12 @@ export default class ArticleEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleConfigApply (e, newConfig) {
|
||||
this.setState({
|
||||
switchPreview: newConfig['switch-preview']
|
||||
})
|
||||
}
|
||||
|
||||
resetCursorPosition () {
|
||||
this.setState({
|
||||
cursorPosition: null,
|
||||
@@ -35,13 +63,15 @@ export default class ArticleEditor extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
switchPreviewMode () {
|
||||
switchPreviewMode (isTemporary = false) {
|
||||
if (this.props.article.mode !== 'markdown') return true
|
||||
let cursorPosition = this.refs.editor.getCursorPosition()
|
||||
let firstVisibleRow = this.refs.editor.getFirstVisibleRow()
|
||||
this.setState({
|
||||
status: PREVIEW_MODE,
|
||||
cursorPosition,
|
||||
firstVisibleRow
|
||||
firstVisibleRow,
|
||||
isTemporary: isTemporary
|
||||
}, function () {
|
||||
let previewEl = ReactDOM.findDOMNode(this.refs.preview)
|
||||
let anchors = previewEl.querySelectorAll('.lineAnchor')
|
||||
@@ -55,43 +85,27 @@ export default class ArticleEditor extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
switchEditMode () {
|
||||
switchEditMode (isTemporary = false) {
|
||||
this.setState({
|
||||
status: EDIT_MODE
|
||||
status: EDIT_MODE,
|
||||
isTemporary: false
|
||||
}, function () {
|
||||
if (this.state.cursorPosition != null) {
|
||||
this.refs.editor.moveCursorTo(this.state.cursorPosition.row, this.state.cursorPosition.column)
|
||||
this.refs.editor.scrollToLine(this.state.firstVisibleRow)
|
||||
}
|
||||
this.refs.editor.editor.focus()
|
||||
|
||||
if (!isTemporary) activityRecord.emit('ARTICLE_UPDATE', this.props.article)
|
||||
})
|
||||
}
|
||||
|
||||
handlePreviewMouseDown (e) {
|
||||
if (e.button === 2) return true
|
||||
this.isDrag = false
|
||||
this.isMouseDown = true
|
||||
this.moveCount = 0
|
||||
}
|
||||
|
||||
handlePreviewMouseMove () {
|
||||
if (this.isMouseDown) {
|
||||
this.moveCount++
|
||||
if (this.moveCount > 5) {
|
||||
this.isDrag = true
|
||||
}
|
||||
handleBlurCodeEditor (e) {
|
||||
let isFocusingToThis = e.relatedTarget === ReactDOM.findDOMNode(this)
|
||||
if (isFocusingToThis || this.state.switchPreview !== 'blur') {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
handlePreviewMouseUp () {
|
||||
this.isMouseDown = false
|
||||
this.moveCount = 0
|
||||
if (!this.isDrag) {
|
||||
this.switchEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
handleBlurCodeEditor () {
|
||||
let { article } = this.props
|
||||
if (article.mode === 'markdown') {
|
||||
this.switchPreviewMode()
|
||||
@@ -102,36 +116,115 @@ export default class ArticleEditor extends React.Component {
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
handleRightClick (e) {
|
||||
let { article } = this.props
|
||||
if (this.state.switchPreview === 'rightclick' && article.mode === 'markdown') {
|
||||
if (this.state.status === EDIT_MODE) this.switchPreviewMode()
|
||||
else this.switchEditMode()
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
let { article } = this.props
|
||||
let showPreview = article.mode === 'markdown' && this.state.status === PREVIEW_MODE
|
||||
if (!showPreview) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch (this.state.switchPreview) {
|
||||
case 'blur':
|
||||
switch (e.button) {
|
||||
case 0:
|
||||
this.isMouseDown = false
|
||||
this.moveCount = 0
|
||||
if (!this.isDrag) {
|
||||
this.switchEditMode()
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
if (this.state.isTemporary) this.switchEditMode(true)
|
||||
}
|
||||
break
|
||||
case 'rightclick':
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseMove (e) {
|
||||
let { article } = this.props
|
||||
let showPreview = article.mode === 'markdown' && this.state.status === PREVIEW_MODE
|
||||
if (!showPreview) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.state.switchPreview === 'blur' && this.isMouseDown) {
|
||||
this.moveCount++
|
||||
if (this.moveCount > 5) {
|
||||
this.isDrag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDowm (e) {
|
||||
let { article } = this.props
|
||||
let showPreview = article.mode === 'markdown' && this.state.status === PREVIEW_MODE
|
||||
if (!showPreview) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch (this.state.switchPreview) {
|
||||
case 'blur':
|
||||
switch (e.button) {
|
||||
case 0:
|
||||
this.isDrag = false
|
||||
this.isMouseDown = true
|
||||
this.moveCount = 0
|
||||
break
|
||||
case 2:
|
||||
if (this.state.status === EDIT_MODE && this.props.article.mode === 'markdown') {
|
||||
this.switchPreviewMode(true)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'rightclick':
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let { article } = this.props
|
||||
let showPreview = article.mode === 'markdown' && this.state.status === PREVIEW_MODE
|
||||
if (showPreview) {
|
||||
return (
|
||||
<div className='ArticleEditor'>
|
||||
<MarkdownPreview
|
||||
ref='preview'
|
||||
onMouseUp={e => this.handlePreviewMouseUp(e)}
|
||||
onMouseDown={e => this.handlePreviewMouseDown(e)}
|
||||
onMouseMove={e => this.handlePreviewMouseMove(e)}
|
||||
content={article.content}
|
||||
/>
|
||||
<div className='ArticleDetail-panel-content-tooltip'>Click to Edit</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ArticleEditor'>
|
||||
<CodeEditor
|
||||
ref='editor'
|
||||
onBlur={e => this.handleBlurCodeEditor(e)}
|
||||
onChange={value => this.handleCodeEditorChange(value)}
|
||||
article={article}
|
||||
/>
|
||||
<div
|
||||
tabIndex='5'
|
||||
onContextMenu={e => this.handleRightClick(e)}
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
onMouseMove={e => this.handleMouseMove(e)}
|
||||
onMouseDown={e => this.handleMouseDowm(e)}
|
||||
className='ArticleEditor'
|
||||
>
|
||||
{showPreview
|
||||
? <MarkdownPreview
|
||||
ref='preview'
|
||||
content={article.content}
|
||||
/>
|
||||
: <CodeEditor
|
||||
ref='editor'
|
||||
onBlur={e => this.handleBlurCodeEditor(e)}
|
||||
onChange={value => this.handleCodeEditorChange(value)}
|
||||
article={article}
|
||||
/>
|
||||
}
|
||||
{article.mode === 'markdown'
|
||||
? (
|
||||
<div className='ArticleDetail-panel-content-tooltip'>Press ESC to watch Preview</div>
|
||||
)
|
||||
? <div className='ArticleDetail-panel-content-tooltip' children={
|
||||
showPreview
|
||||
? this.state.switchPreview === 'blur'
|
||||
? 'Click to Edit'
|
||||
: 'Right Click to Edit'
|
||||
: this.state.switchPreview === 'blur'
|
||||
? 'Press ESC to Watch Preview'
|
||||
: 'Right Click to Watch Preview'
|
||||
}
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
@@ -145,5 +238,6 @@ ArticleEditor.propTypes = {
|
||||
key: PropTypes.string,
|
||||
mode: PropTypes.string
|
||||
}),
|
||||
onChange: PropTypes.func
|
||||
onChange: PropTypes.func,
|
||||
parent: PropTypes.object
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ import moment from 'moment'
|
||||
import _ from 'lodash'
|
||||
import {
|
||||
switchFolder,
|
||||
updateArticle,
|
||||
// cacheArticle,
|
||||
// saveArticle,
|
||||
// uncacheArticle
|
||||
updateArticle
|
||||
} from '../../actions'
|
||||
import linkState from 'browser/lib/linkState'
|
||||
import TagSelect from 'browser/components/TagSelect'
|
||||
@@ -18,22 +15,12 @@ import DeleteArticleModal from '../../modal/DeleteArticleModal'
|
||||
import ArticleEditor from './ArticleEditor'
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
import fetchConfig from 'browser/lib/fetchConfig'
|
||||
|
||||
// const remote = electron.remote
|
||||
// const { Menu, MenuItem } = remote
|
||||
// const othersMenu = new Menu()
|
||||
// othersMenu.append(new MenuItem({
|
||||
// label: 'Delete Post',
|
||||
// click: function () {
|
||||
// remote.getCurrentWebContents().send('detail-delete')
|
||||
// }
|
||||
// }))
|
||||
// othersMenu.append(new MenuItem({
|
||||
// label: 'Discard Change',
|
||||
// click: function (item) {
|
||||
// remote.getCurrentWebContents().send('detail-uncache')
|
||||
// }
|
||||
// }))
|
||||
let config = fetchConfig()
|
||||
ipc.on('config-apply', function (e, newConfig) {
|
||||
config = newConfig
|
||||
})
|
||||
|
||||
const BRAND_COLOR = '#18AF90'
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
@@ -80,10 +67,6 @@ export default class ArticleDetail extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.saveHandler = e => {
|
||||
if (isModalOpen()) return true
|
||||
this.handleSaveButtonClick()
|
||||
}
|
||||
this.deleteHandler = e => {
|
||||
if (isModalOpen()) return true
|
||||
this.handleDeleteButtonClick()
|
||||
@@ -102,10 +85,16 @@ export default class ArticleDetail extends React.Component {
|
||||
if (isModalOpen()) return true
|
||||
if (this.refs.editor) this.refs.editor.switchEditMode()
|
||||
}
|
||||
this.previewHandler = e => {
|
||||
if (isModalOpen()) return true
|
||||
if (this.refs.editor) this.refs.editor.switchPreviewMode()
|
||||
}
|
||||
this.configApplyHandler = (e, config) => this.handleConfigApply(e, config)
|
||||
|
||||
this.state = {
|
||||
article: Object.assign({content: ''}, props.activeArticle),
|
||||
openShareDropdown: false
|
||||
openShareDropdown: false,
|
||||
fontFamily: config['editor-font-family']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,21 +104,22 @@ export default class ArticleDetail extends React.Component {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
// ipc.on('detail-save', this.saveHandler)
|
||||
ipc.on('detail-delete', this.deleteHandler)
|
||||
ipc.on('detail-uncache', this.uncacheHandler)
|
||||
ipc.on('detail-title', this.titleHandler)
|
||||
ipc.on('detail-edit', this.editHandler)
|
||||
ipc.on('detail-preview', this.previewHandler)
|
||||
ipc.on('config-apply', this.configApplyHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
clearInterval(this.refreshTimer)
|
||||
|
||||
// ipc.removeListener('detail-save', this.saveHandler)
|
||||
ipc.removeListener('detail-delete', this.deleteHandler)
|
||||
ipc.removeListener('detail-uncache', this.uncacheHandler)
|
||||
ipc.removeListener('detail-title', this.titleHandler)
|
||||
ipc.removeListener('detail-edit', this.editHandler)
|
||||
ipc.removeListener('detail-preview', this.previewHandler)
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
@@ -142,6 +132,12 @@ export default class ArticleDetail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleConfigApply (e, config) {
|
||||
this.setState({
|
||||
fontFamily: config['editor-font-family']
|
||||
})
|
||||
}
|
||||
|
||||
renderEmpty () {
|
||||
return (
|
||||
<div className='ArticleDetail empty'>
|
||||
@@ -165,7 +161,7 @@ export default class ArticleDetail extends React.Component {
|
||||
|
||||
dispatch(updateArticle(article))
|
||||
|
||||
let targetFolderKey = this.state.article.FolderKey
|
||||
let targetFolderKey = e.target.value
|
||||
if (status.targetFolders.length > 0) {
|
||||
let targetFolder = _.findWhere(folders, {key: targetFolderKey})
|
||||
dispatch(switchFolder(targetFolder.name))
|
||||
@@ -328,6 +324,9 @@ export default class ArticleDetail extends React.Component {
|
||||
ref='title'
|
||||
value={activeArticle.title}
|
||||
onChange={e => this.handleTitleChange(e)}
|
||||
style={{
|
||||
fontFamily: this.state.fontFamily
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ModeSelect
|
||||
|
||||
@@ -4,6 +4,7 @@ import ExternalLink from 'browser/components/ExternalLink'
|
||||
import { setSearchFilter, clearSearch, toggleTutorial, saveArticle, switchFolder } from '../actions'
|
||||
import { isModalOpen } from 'browser/lib/modal'
|
||||
import keygen from 'browser/lib/keygen'
|
||||
import activityRecord from 'browser/lib/activityRecord'
|
||||
|
||||
const electron = require('electron')
|
||||
const remote = electron.remote
|
||||
@@ -167,6 +168,7 @@ export default class ArticleTopBar extends React.Component {
|
||||
dispatch(saveArticle(newArticle.key, newArticle, true))
|
||||
if (isFolderFilterApplied) dispatch(switchFolder(targetFolders[0].name))
|
||||
remote.getCurrentWebContents().send('detail-title')
|
||||
activityRecord.emit('ARTICLE_CREATE')
|
||||
}
|
||||
|
||||
handleTutorialButtonClick (e) {
|
||||
@@ -229,8 +231,8 @@ export default class ArticleTopBar extends React.Component {
|
||||
<ExternalLink className='ArticleTopBar-right-links-button-dropdown-item' href='https://b00st.io'>
|
||||
<i className='fa fa-fw fa-home'/>Boost official page
|
||||
</ExternalLink>
|
||||
<ExternalLink className='ArticleTopBar-right-links-button-dropdown-item' href='https://github.com/BoostIO/boost-app-discussions/issues'>
|
||||
<i className='fa fa-fw fa-bullhorn'/> Discuss
|
||||
<ExternalLink className='ArticleTopBar-right-links-button-dropdown-item' href='https://github.com/BoostIO/Boostnote/issues'>
|
||||
<i className='fa fa-fw fa-github'/> Issues
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -19,9 +19,27 @@ export default class MainContainer extends React.Component {
|
||||
ipc.send('update-app', 'Deal with it.')
|
||||
}
|
||||
|
||||
handleWheel (e) {
|
||||
if (e.ctrlKey && global.process.platform !== 'darwin') {
|
||||
if (window.document.body.style.zoom == null) {
|
||||
window.document.body.style.zoom = 1
|
||||
}
|
||||
|
||||
let zoom = Number(window.document.body.style.zoom)
|
||||
if (e.deltaY > 0 && zoom < 4) {
|
||||
document.body.style.zoom = zoom + 0.05
|
||||
} else if (e.deltaY < 0 && zoom > 0.5) {
|
||||
document.body.style.zoom = zoom - 0.05
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='Main'>
|
||||
<div
|
||||
className='Main'
|
||||
onWheel={(e) => this.handleWheel(e)}
|
||||
>
|
||||
{this.state.updateAvailable ? (
|
||||
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
|
||||
) : null}
|
||||
|
||||
@@ -5,17 +5,35 @@ import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
require('../styles/main/index.styl')
|
||||
import { openModal } from 'browser/lib/modal'
|
||||
import Tutorial from './modal/Tutorial'
|
||||
import OSSAnnounceModal from './modal/OSSAnnounceModal'
|
||||
import activityRecord from 'browser/lib/activityRecord'
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
const path = require('path')
|
||||
const remote = electron.remote
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.addEventListener('keydown', function (e) {
|
||||
if (e.keyCode === 73 && e.metaKey && e.altKey) {
|
||||
remote.getCurrentWindow().toggleDevTools()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
activityRecord.init()
|
||||
window.addEventListener('online', function () {
|
||||
ipc.send('check-update', 'check-update')
|
||||
})
|
||||
|
||||
document.addEventListener('drop', function (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.addEventListener('dragover', function (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
})
|
||||
|
||||
function notify (title, options) {
|
||||
if (process.platform === 'win32') {
|
||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||
@@ -50,9 +68,10 @@ ReactDOM.render((
|
||||
loadingCover.parentNode.removeChild(loadingCover)
|
||||
let status = JSON.parse(localStorage.getItem('status'))
|
||||
if (status == null) status = {}
|
||||
if (!status.introWatched) {
|
||||
openModal(Tutorial)
|
||||
status.introWatched = true
|
||||
|
||||
if (!status.ossAnnounceWatched) {
|
||||
openModal(OSSAnnounceModal)
|
||||
status.ossAnnounceWatched = true
|
||||
localStorage.setItem('status', JSON.stringify(status))
|
||||
}
|
||||
})
|
||||
|
||||
29
browser/main/modal/OSSAnnounceModal.js
Normal file
29
browser/main/modal/OSSAnnounceModal.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ExternalLink from 'browser/components/ExternalLink'
|
||||
|
||||
export default class OSSAnnounceModal extends React.Component {
|
||||
handleCloseBtnClick (e) {
|
||||
this.props.close()
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div className='OSSAnnounceModal modal'>
|
||||
|
||||
<div className='OSSAnnounceModal-title'>Boostnote has been Open-sourced</div>
|
||||
|
||||
<ExternalLink className='OSSAnnounceModal-link' href='https://github.com/BoostIO/Boostnote'>
|
||||
https://github.com/BoostIO/Boostnote
|
||||
</ExternalLink>
|
||||
|
||||
<button
|
||||
className='OSSAnnounceModal-closeBtn'
|
||||
onClick={(e) => this.handleCloseBtnClick(e)}
|
||||
>Close</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
OSSAnnounceModal.propTypes = {
|
||||
close: PropTypes.func
|
||||
}
|
||||
@@ -138,9 +138,9 @@ export default class AppSettingTab extends React.Component {
|
||||
<label>Editor Font Family</label>
|
||||
<input valueLink={this.linkState('config.editor-font-family')} onKeyDown={e => this.handleConfigKeyDown(e)} type='text'/>
|
||||
</div>
|
||||
<div className='sectionSelect'>
|
||||
<div className='sectionMultiSelect'>
|
||||
<label>Editor Indent Style</label>
|
||||
<div className='sectionSelect-input'>
|
||||
<div className='sectionMultiSelect-input'>
|
||||
type
|
||||
<select valueLink={this.linkState('config.editor-indent-type')}>
|
||||
<option value='space'>Space</option>
|
||||
@@ -162,8 +162,15 @@ export default class AppSettingTab extends React.Component {
|
||||
<label>Preview Font Family</label>
|
||||
<input valueLink={this.linkState('config.preview-font-family')} onKeyDown={e => this.handleConfigKeyDown(e)} type='text'/>
|
||||
</div>
|
||||
<div className='sectionSelect'>
|
||||
<label>Switching Preview</label>
|
||||
<select valueLink={this.linkState('config.switch-preview')}>
|
||||
<option value='blur'>When Editor Blurred</option>
|
||||
<option value='rightclick'>When Right Clicking</option>
|
||||
</select>
|
||||
</div>
|
||||
{
|
||||
true// !OSX
|
||||
global.process.platform === 'win32'
|
||||
? (
|
||||
<div className='sectionCheck'>
|
||||
<label><input onClick={e => this.handleDisableDirectWriteClick(e)} checked={this.state.config['disable-direct-write']} disabled={OSX} type='checkbox'/>Disable Direct Write<span className='sectionCheck-warn'>It will be applied after restarting</span></label>
|
||||
|
||||
@@ -1,122 +1,23 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import clientKey from 'browser/lib/clientKey'
|
||||
import linkState from 'browser/lib/linkState'
|
||||
import _ from 'lodash'
|
||||
import { request, SERVER_URL } from 'browser/lib/api'
|
||||
|
||||
const FORM_MODE = 'FORM_MODE'
|
||||
const DONE_MODE = 'DONE_MODE'
|
||||
import ExternalLink from 'browser/components/ExternalLink'
|
||||
|
||||
export default class ContactTab extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
title: '',
|
||||
content: '',
|
||||
email: '',
|
||||
mode: FORM_MODE,
|
||||
alert: null
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
let titleInput = ReactDOM.findDOMNode(this.refs.title)
|
||||
if (titleInput != null) titleInput.focus()
|
||||
}
|
||||
|
||||
handleBackButtonClick (e) {
|
||||
this.setState({
|
||||
mode: FORM_MODE
|
||||
})
|
||||
}
|
||||
|
||||
handleSendButtonClick (e) {
|
||||
let input = _.pick(this.state, ['title', 'content', 'email'])
|
||||
input.clientKey = clientKey.get()
|
||||
|
||||
this.setState({
|
||||
alert: {
|
||||
type: 'info',
|
||||
message: 'Sending...'
|
||||
}
|
||||
}, () => {
|
||||
request.post(SERVER_URL + 'apis/inquiry')
|
||||
.send(input)
|
||||
.then(res => {
|
||||
console.log('sent')
|
||||
this.setState({
|
||||
title: '',
|
||||
content: '',
|
||||
mode: DONE_MODE,
|
||||
alert: null
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
this.setState({
|
||||
alert: {
|
||||
type: 'error',
|
||||
message: 'Can\'t connect to API server.'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.error(err)
|
||||
this.setState({
|
||||
alert: {
|
||||
type: 'error',
|
||||
message: err.message
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
switch (this.state.mode) {
|
||||
case DONE_MODE:
|
||||
return (
|
||||
<div className='ContactTab content done'>
|
||||
<div className='message'>
|
||||
<i className='checkIcon fa fa-check-circle'/><br/>
|
||||
Your message has been sent successfully!!
|
||||
</div>
|
||||
<div className='control'>
|
||||
<button onClick={e => this.handleBackButtonClick(e)}>Back to Contact form</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case FORM_MODE:
|
||||
default:
|
||||
let alertElement = this.state.alert != null
|
||||
? (
|
||||
<div className={'alert ' + this.state.alert.type}>{this.state.alert.message}</div>
|
||||
)
|
||||
: null
|
||||
return (
|
||||
<div className='ContactTab content form'>
|
||||
<div className='title'>Contact form</div>
|
||||
<div className='description'>
|
||||
Your feedback is highly appreciated and will help us to improve our app. :D
|
||||
</div>
|
||||
<div className='iptGroup'>
|
||||
<input ref='title' valueLink={this.linkState('title')} placeholder='Title' type='text'/>
|
||||
</div>
|
||||
<div className='iptGroup'>
|
||||
<textarea valueLink={this.linkState('content')} placeholder='Content'/>
|
||||
</div>
|
||||
<div className='iptGroup'>
|
||||
<input valueLink={this.linkState('email')} placeholder='E-mail (Optional)' type='email'/>
|
||||
</div>
|
||||
<div className='formControl'>
|
||||
<button onClick={e => this.handleSendButtonClick(e)} className='primary'>Send</button>
|
||||
{alertElement}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className='ContactTab content'>
|
||||
<div className='title'>Contact</div>
|
||||
<p>
|
||||
- Issues: <ExternalLink href='https://github.com/BoostIO/Boostnote/issues'>https://github.com/BoostIO/Boostnote/issues</ExternalLink>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -257,9 +257,10 @@ infoButton()
|
||||
width 150px
|
||||
max-height 150px
|
||||
background-color white
|
||||
z-index 5
|
||||
z-index 50
|
||||
border 1px solid borderColor
|
||||
border-radius 5px
|
||||
overflow-y auto
|
||||
&>button
|
||||
width 100%
|
||||
display block
|
||||
|
||||
@@ -16,7 +16,7 @@ articleItemColor = #777
|
||||
.ArticleList-item
|
||||
border solid 2px transparent
|
||||
position relative
|
||||
height 110px
|
||||
min-height 110px
|
||||
width 100%
|
||||
cursor pointer
|
||||
transition 0.1s
|
||||
@@ -68,18 +68,27 @@ articleItemColor = #777
|
||||
code
|
||||
font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace
|
||||
.ArticleList-item-bottom
|
||||
overflow-x auto
|
||||
white-space nowrap
|
||||
padding-top 6px
|
||||
padding-bottom 5px
|
||||
.tags
|
||||
color articleItemColor
|
||||
height 14px
|
||||
line-height 18px
|
||||
word-wrap break-word
|
||||
clearfix()
|
||||
i.fa-tags
|
||||
display inline
|
||||
float left
|
||||
padding 2px 2px 0 0
|
||||
height 14px
|
||||
line-height 13px
|
||||
a
|
||||
background-color brandColor
|
||||
float left
|
||||
color white
|
||||
border-radius 2px
|
||||
padding 1px 5px
|
||||
margin 2px
|
||||
height 14px
|
||||
line-height 13px
|
||||
font-size 10px
|
||||
opacity 0.8
|
||||
&:hover
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
global-reset()
|
||||
@import '../shared/*'
|
||||
@import './ArticleNavigator'
|
||||
@import './ArticleTopbar'
|
||||
@import './ArticleTopBar'
|
||||
@import './ArticleList'
|
||||
@import './ArticleDetail'
|
||||
@import './modal/*'
|
||||
@@ -126,3 +126,28 @@ textarea.block-input
|
||||
margin-left -107px
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.OSSAnnounceModal
|
||||
height 250
|
||||
text-align center
|
||||
.OSSAnnounceModal-title
|
||||
font-size 32px
|
||||
padding 45px 0
|
||||
|
||||
.OSSAnnounceModal-link
|
||||
display block
|
||||
font-size 20px
|
||||
margin 25px 0 65px
|
||||
.OSSAnnounceModal-closeBtn
|
||||
display block
|
||||
margin 0 auto
|
||||
border none
|
||||
border-radius 5px
|
||||
width 150px
|
||||
height 33px
|
||||
background-color brandColor
|
||||
color white
|
||||
opacity 0.7
|
||||
&:hover
|
||||
opacity 1
|
||||
background-color lighten(brandColor, 10%)
|
||||
|
||||
@@ -60,7 +60,7 @@ iptFocusBorderColor = #369DCD
|
||||
left 180px
|
||||
overflow-y auto
|
||||
&>.section
|
||||
padding 10px
|
||||
padding 10px 20px
|
||||
border-bottom 1px solid borderColor
|
||||
overflow-y auto
|
||||
&:nth-last-child(1)
|
||||
@@ -102,7 +102,6 @@ iptFocusBorderColor = #369DCD
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
&>.sectionSelect
|
||||
|
||||
margin-bottom 5px
|
||||
clearfix()
|
||||
height 33px
|
||||
@@ -111,7 +110,28 @@ iptFocusBorderColor = #369DCD
|
||||
padding-left 15px
|
||||
float left
|
||||
line-height 33px
|
||||
.sectionSelect-input
|
||||
select
|
||||
float left
|
||||
width 200px
|
||||
height 25px
|
||||
margin-top 4px
|
||||
border-radius 5px
|
||||
border 1px solid borderColor
|
||||
padding 0 10px
|
||||
font-size 14px
|
||||
outline none
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
&>.sectionMultiSelect
|
||||
margin-bottom 5px
|
||||
clearfix()
|
||||
height 33px
|
||||
label
|
||||
width 150px
|
||||
padding-left 15px
|
||||
float left
|
||||
line-height 33px
|
||||
.sectionMultiSelect-input
|
||||
float left
|
||||
select
|
||||
width 80px
|
||||
@@ -157,69 +177,14 @@ iptFocusBorderColor = #369DCD
|
||||
color errorTextColor
|
||||
background-color errorBackgroundColor
|
||||
&.ContactTab
|
||||
&.done
|
||||
.message
|
||||
margin-top 75px
|
||||
margin-bottom 15px
|
||||
text-align center
|
||||
font-size 22px
|
||||
.checkIcon
|
||||
margin-bottom 15px
|
||||
font-size 144px
|
||||
color brandColor
|
||||
text-align center
|
||||
.control
|
||||
text-align center
|
||||
button
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
background-color white
|
||||
padding 15px 15px
|
||||
font-size 14px
|
||||
&:hover
|
||||
background-color darken(white, 10%)
|
||||
&.form
|
||||
padding 10px
|
||||
.title
|
||||
font-size 18px
|
||||
color brandColor
|
||||
margin-top 10px
|
||||
margin-bottom 10px
|
||||
.description
|
||||
margin-bottom 15px
|
||||
.iptGroup
|
||||
margin-bottom 10px
|
||||
input, textarea
|
||||
border-radius 5px
|
||||
border 1px solid borderColor
|
||||
font-size 14px
|
||||
outline none
|
||||
padding 10px 15px
|
||||
width 100%
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
textarea
|
||||
resize vertical
|
||||
min-height 150px
|
||||
.formControl
|
||||
clearfix()
|
||||
.alert
|
||||
float right
|
||||
padding 10px 15px
|
||||
margin 0 5px 0
|
||||
font-size 14px
|
||||
line-height normal
|
||||
button
|
||||
padding 10px 15px
|
||||
background-color brandColor
|
||||
color white
|
||||
font-size 14px
|
||||
border-radius 5px
|
||||
border none
|
||||
float right
|
||||
&:hover
|
||||
background-color lighten(brandColor, 10%)
|
||||
|
||||
padding 10px
|
||||
.title
|
||||
font-size 18px
|
||||
color brandColor
|
||||
margin-top 10px
|
||||
margin-bottom 10px
|
||||
p
|
||||
line-height 2
|
||||
&.AppSettingTab
|
||||
.description
|
||||
marked()
|
||||
|
||||
@@ -26,6 +26,8 @@ marked()
|
||||
margin -5px
|
||||
transition .1s
|
||||
display inline-block
|
||||
img
|
||||
vertical-align sub
|
||||
&:hover
|
||||
color lighten(brandColor, 5%)
|
||||
text-decoration underline
|
||||
@@ -48,12 +50,12 @@ marked()
|
||||
*:not(a.lineAnchor) + h1, *:not(a.lineAnchor) + h2, *:not(a.lineAnchor) + h3, *:not(a.lineAnchor) + h4, *:not(a.lineAnchor) + h5, *:not(a.lineAnchor) + h6
|
||||
margin-top 25px
|
||||
h1
|
||||
font-size 2em
|
||||
font-size 1.8em
|
||||
border-bottom solid 2px borderColor
|
||||
line-height 2.333em
|
||||
line-height 2em
|
||||
h2
|
||||
font-size 1.66em
|
||||
line-height 2.07em
|
||||
line-height 1.8em
|
||||
h3
|
||||
font-size 1.33em
|
||||
line-height 1.6625em
|
||||
@@ -78,7 +80,7 @@ marked()
|
||||
font-weight bold
|
||||
em, i
|
||||
font-style italic
|
||||
s
|
||||
s, del, strike
|
||||
text-decoration line-through
|
||||
u
|
||||
text-decoration underline
|
||||
|
||||
35
contributing.md
Normal file
35
contributing.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Contributing to Boostnote
|
||||
|
||||
> English below.
|
||||
|
||||
## About Pull Request
|
||||
|
||||
### やり方
|
||||
|
||||
現状特に`dev`ブランチを用意しないつもりなので、最新VersionのブランチにPullrequestを送ってください。
|
||||
|
||||
### Pull requsetの著作権
|
||||
|
||||
Pull requestをすることはその変化分のコードの著作権をMAISIN&CO.に譲渡することに同意することになります。
|
||||
|
||||
アプリケーションのLicenseのをいつでも変える選択肢を残したいからです。
|
||||
しかし、これはいずれかBoostnoteが有料の商用アプリになる可能性がある話ではありません。
|
||||
もし、このアプリケーションで金を稼ごうとするならBoostnote専用のCloud storageの提供やMobile appとの連動、何か特殊なプレミアム機能の提供など形になると思います。
|
||||
現在考えられているのは、GPL v3の場合、他のライセンスとの互換が不可能であるため、もしより自由なLicense(BSD, MIT)に変える時に改めて著作権者としてライセンスし直す選択肢を残したいぐらいのイメージです。
|
||||
|
||||
---
|
||||
|
||||
# Contributing to Boostnote(ENG)
|
||||
|
||||
## About Pull Request
|
||||
|
||||
### How to
|
||||
|
||||
Make a new PR to the branch named latest version. This is because there is no `dev` branch currently.
|
||||
|
||||
### Copyright of Pull Request
|
||||
|
||||
If you make a pull request, It means you agree to transfer the copyright of the code changes to MAISIN&CO.
|
||||
|
||||
It doesn't mean Boostnote will become a paid app. If we want to earn some money, We will try other way, which is some kind of cloud storage, Mobile app integration or some SPECIAL features.
|
||||
Because GPL v3 is too strict to be compatible with any other License, We thought this is needed to replace the license with much freer one(like BSD, MIT) somewhen.
|
||||
@@ -11,6 +11,7 @@ const defaultConfig = {
|
||||
'editor-indent-size': '4',
|
||||
'preview-font-size': '14',
|
||||
'preview-font-family': 'Lato',
|
||||
'switch-preview': 'blur',
|
||||
'disable-direct-write': false
|
||||
}
|
||||
const configFile = 'config.json'
|
||||
|
||||
@@ -75,16 +75,6 @@ var view = {
|
||||
click: function () {
|
||||
BrowserWindow.getFocusedWindow().reload()
|
||||
}
|
||||
// },
|
||||
// {
|
||||
// label: 'Toggle Developer Tools',
|
||||
// accelerator: (function () {
|
||||
// if (process.platform === 'darwin') return 'Alt+Command+I'
|
||||
// else return 'Ctrl+Shift+I'
|
||||
// })(),
|
||||
// click: function (item, focusedWindow) {
|
||||
// if (focusedWindow) BrowserWindow.getFocusedWindow().toggleDevTools()
|
||||
// }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -79,12 +79,12 @@ var config = {
|
||||
show: false,
|
||||
frame: false,
|
||||
resizable: false,
|
||||
'zoom-factor': 1.0,
|
||||
'web-preferences': {
|
||||
'overlay-scrollbars': true,
|
||||
'skip-taskbar': true
|
||||
zoomFactor: 1.0,
|
||||
webPreferences: {
|
||||
blinkFeatures: 'OverlayScrollbars'
|
||||
},
|
||||
'standard-window': false
|
||||
skipTaskbar: true,
|
||||
standardWindow: false
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
var scriptEl=document.createElement('script')
|
||||
scriptEl.setAttribute("type","text/javascript")
|
||||
scriptEl.setAttribute("src", scriptUrl)
|
||||
document.getElementsByTagName("head")[0].appendChild(scriptEl)
|
||||
document.body.appendChild(scriptEl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,12 +6,13 @@ const globalShortcut = electron.globalShortcut
|
||||
const jetpack = require('fs-jetpack')
|
||||
const mainWindow = require('./main-window')
|
||||
const nodeIpc = require('@rokt33r/node-ipc')
|
||||
const _ = require('lodash')
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
const defaultKeymap = {
|
||||
toggleFinder: OSX ? 'Cmd + alt + s' : 'Super + alt + s',
|
||||
toggleMain: OSX ? 'Cmd + alt + v' : 'Super + alt + v'
|
||||
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
|
||||
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
||||
}
|
||||
const keymapFilename = 'keymap.json'
|
||||
|
||||
@@ -66,35 +67,43 @@ function toggleMain () {
|
||||
mainWindow.minimize()
|
||||
mainWindow.restore()
|
||||
}
|
||||
mainWindow.webContents.send('list-focus')
|
||||
mainWindow.webContents.send('top-focus-search')
|
||||
}
|
||||
}
|
||||
|
||||
// Init
|
||||
global.keymap = Object.assign({}, defaultKeymap, getKeymap())
|
||||
|
||||
function registerKey (name, callback, broadcast) {
|
||||
if (broadcast == null) broadcast = true
|
||||
|
||||
try {
|
||||
function registerKey (name, callback) {
|
||||
if (_.isString(global.keymap[name]) && global.keymap[name].trim().length > 0) {
|
||||
globalShortcut.register(global.keymap[name], callback)
|
||||
if (broadcast) {
|
||||
mainWindow.webContents.send('APP_SETTING_DONE', {})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
if (broadcast) {
|
||||
mainWindow.webContents.send('APP_SETTING_ERROR', {
|
||||
message: 'Failed to apply hotkey: Invalid format'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerAllKeys (broadcast) {
|
||||
if (broadcast == null) broadcast = true
|
||||
registerKey('toggleFinder', toggleFinder, broadcast)
|
||||
registerKey('toggleMain', toggleMain, broadcast)
|
||||
|
||||
var errors = []
|
||||
try {
|
||||
registerKey('toggleFinder', toggleFinder)
|
||||
} catch (err) {
|
||||
errors.push('toggleFinder')
|
||||
}
|
||||
try {
|
||||
registerKey('toggleMain', toggleMain)
|
||||
} catch (err) {
|
||||
errors.push('toggleMain')
|
||||
}
|
||||
|
||||
if (broadcast) {
|
||||
if (errors.length === 0) {
|
||||
mainWindow.webContents.send('APP_SETTING_DONE', {})
|
||||
} else {
|
||||
mainWindow.webContents.send('APP_SETTING_ERROR', {
|
||||
message: 'Failed to apply hotkey: ' + errors.join(' ')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerAllKeys(false)
|
||||
@@ -105,4 +114,3 @@ ipc.on('hotkeyUpdated', function (event, newKeymap) {
|
||||
globalShortcut.unregisterAll()
|
||||
registerAllKeys()
|
||||
})
|
||||
|
||||
|
||||
@@ -149,16 +149,6 @@ var view = {
|
||||
click: function () {
|
||||
BrowserWindow.getFocusedWindow().reload()
|
||||
}
|
||||
// },
|
||||
// {
|
||||
// label: 'Toggle Developer Tools',
|
||||
// accelerator: (function () {
|
||||
// if (process.platform === 'darwin') return 'Alt+Command+I'
|
||||
// else return 'Ctrl+Shift+I'
|
||||
// })(),
|
||||
// click: function (item, focusedWindow) {
|
||||
// if (focusedWindow) BrowserWindow.getFocusedWindow().toggleDevTools()
|
||||
// }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ const path = require('path')
|
||||
var mainWindow = new BrowserWindow({
|
||||
width: 1080,
|
||||
height: 720,
|
||||
'zoom-factor': 1.0,
|
||||
'web-preferences': {
|
||||
'overlay-scrollbars': true
|
||||
zoomFactor: 1.0,
|
||||
webPreferences: {
|
||||
blinkFeatures: 'OverlayScrollbars'
|
||||
},
|
||||
'standard-window': false
|
||||
standardWindow: false
|
||||
})
|
||||
|
||||
const url = path.resolve(__dirname, './main.html')
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
var scriptEl = document.createElement('script')
|
||||
scriptEl.setAttribute("type","text/javascript")
|
||||
scriptEl.setAttribute("src", scriptUrl)
|
||||
document.getElementsByTagName("head")[0].appendChild(scriptEl)
|
||||
document.body.appendChild(scriptEl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
11
package.json
11
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "boost",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.8",
|
||||
"description": "Boostnote",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js"
|
||||
},
|
||||
"config": {
|
||||
"electron-version": "0.35.4"
|
||||
"electron-version": "0.36.11"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -39,6 +39,7 @@
|
||||
"highlight.js": "^8.9.1",
|
||||
"lodash": "^3.10.1",
|
||||
"markdown-it": "^4.4.0",
|
||||
"markdown-it-checkbox": "^1.1.0",
|
||||
"markdown-it-emoji": "^1.1.0",
|
||||
"markdown-it-math": "^3.0.2",
|
||||
"md5": "^2.0.0",
|
||||
@@ -54,8 +55,8 @@
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-react-hmre": "^1.0.1",
|
||||
"css-loader": "^0.19.0",
|
||||
"electron-packager": "^5.1.0",
|
||||
"electron-prebuilt": "^0.35.1",
|
||||
"electron-packager": "^6.0.0",
|
||||
"electron-prebuilt": "^0.36.11",
|
||||
"electron-release": "^2.2.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-electron-installer": "^1.2.0",
|
||||
@@ -72,7 +73,7 @@
|
||||
"webpack": "^1.12.2",
|
||||
"webpack-dev-server": "^1.12.0"
|
||||
},
|
||||
"optionalDependencies": {},
|
||||
"optional": false,
|
||||
"standard": {
|
||||
"ignore": [],
|
||||
"globals": [
|
||||
|
||||
30
readme.md
30
readme.md
@@ -1,8 +1,23 @@
|
||||
# Boostnote
|
||||
|
||||
Hack your memory
|
||||
Simple note app
|
||||
|
||||
## Develope
|
||||
## Progress Kanban
|
||||
|
||||
https://trello.com/b/wJlinZJx/boostnote-todo-list
|
||||
|
||||
This is a public Kanban board. Also everyone can comment here.
|
||||
|
||||
If you want to join us, ask me to add you.
|
||||
|
||||
## Live coding
|
||||
|
||||
I've been broadcasting my work. You can check it from the below links. :smile:
|
||||
|
||||
- [Live Stream](https://www.livecoding.tv/rokt33r/videos/)
|
||||
- [Recorded Videos](https://www.livecoding.tv/rokt33r/videos/)
|
||||
|
||||
## Develop
|
||||
|
||||
1. turn on HMR server
|
||||
|
||||
@@ -18,8 +33,12 @@ npm run hot
|
||||
|
||||
> `npm start` is using compiled scripts. see [Build](#Build) to compile scripts.
|
||||
|
||||
|
||||
## Build
|
||||
|
||||
> '3. Codesign' and '4. Create' installer are needed to deploy this app.
|
||||
> You can skip these steps.
|
||||
|
||||
1. Compile scripts
|
||||
|
||||
compile all browser stuff(Javascript, Stylus).
|
||||
@@ -78,15 +97,18 @@ grunt zip:osx
|
||||
- Webpack
|
||||
... check [`package.json`](./package.json)
|
||||
|
||||
|
||||
## Codestyle
|
||||
|
||||
[](https://github.com/feross/standard)
|
||||
|
||||
## Author
|
||||
|
||||
[Rokt33r(Dick Choi)](https://github.com/rokt33r)
|
||||
[Rokt33r(Dick Choi of MAISIN&CO.)](https://github.com/rokt33r)
|
||||
|
||||
## License
|
||||
## Copyright & License
|
||||
|
||||
Copyright (C) 2016 MAISIN&CO.
|
||||
|
||||
[Check here](./LICENSE).
|
||||
|
||||
|
||||
Submodule submodules/ace updated: 3fb55e8e37...e94cb3c7ff
@@ -32,7 +32,8 @@ var config = {
|
||||
'markdown-it-emoji',
|
||||
'fs-jetpack',
|
||||
'markdown-it-math',
|
||||
'@rokt33r/sanitize-html'
|
||||
'@rokt33r/sanitize-html',
|
||||
'markdown-it-checkbox'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const skeleton = require('./webpack-skeleton')
|
||||
const webpack = require('webpack')
|
||||
const path = require('path')
|
||||
|
||||
var config = Object.assign({}, skeleton, {
|
||||
|
||||
Reference in New Issue
Block a user