mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-22 22:22:12 +00:00
Merge remote-tracking branch 'origin/master' into windows
Conflicts: browser/main/HomePage.js browser/main/HomePage/ArticleNavigator.js webpack.config.js
This commit is contained in:
@@ -16,11 +16,8 @@ export function searchArticle (input) {
|
||||
}
|
||||
}
|
||||
|
||||
export function refreshData () {
|
||||
export function refreshData (data) {
|
||||
console.log('refreshing data')
|
||||
let data = JSON.parse(localStorage.getItem('local'))
|
||||
if (data == null) return null
|
||||
|
||||
let { folders, articles } = data
|
||||
|
||||
return {
|
||||
@@ -31,3 +28,12 @@ export function refreshData () {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
SELECT_ARTICLE,
|
||||
SEARCH_ARTICLE,
|
||||
REFRESH_DATA,
|
||||
selectArticle,
|
||||
searchArticle,
|
||||
refreshData
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
||||
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
||||
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
|
||||
<style>
|
||||
@@ -27,7 +28,8 @@
|
||||
<div id="content"></div>
|
||||
<script src="../../submodules/ace/src-min/ace.js"></script>
|
||||
<script>
|
||||
require('web-frame').setZoomLevelLimits(1, 1)
|
||||
const electron = require('electron')
|
||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
||||
var scriptUrl = process.env.BOOST_ENV === 'development'
|
||||
? 'http://localhost:8080/assets/finder.js'
|
||||
: '../../compiled/finder.js'
|
||||
|
||||
@@ -6,18 +6,17 @@ import { createStore } from 'redux'
|
||||
import FinderInput from './FinderInput'
|
||||
import FinderList from './FinderList'
|
||||
import FinderDetail from './FinderDetail'
|
||||
import { selectArticle, searchArticle, refreshData } from './actions'
|
||||
import actions, { selectArticle, searchArticle } from './actions'
|
||||
import _ from 'lodash'
|
||||
import activityRecord from 'boost/activityRecord'
|
||||
import dataStore from 'boost/dataStore'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote, clipboard, ipcRenderer } = electron
|
||||
|
||||
import remote from 'remote'
|
||||
var hideFinder = remote.getGlobal('hideFinder')
|
||||
import clipboard from 'clipboard'
|
||||
|
||||
var notifier = require('node-notifier')
|
||||
var path = require('path')
|
||||
function getIconPath () {
|
||||
return path.resolve(global.__dirname, '../../resources/favicon-230x230.png')
|
||||
function notify (...args) {
|
||||
return new window.Notification(...args)
|
||||
}
|
||||
|
||||
require('../styles/finder/index.styl')
|
||||
@@ -33,11 +32,20 @@ class FinderMain extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.keyDownHandler = e => this.handleKeyDown(e)
|
||||
document.addEventListener('keydown', this.keyDownHandler)
|
||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
||||
this.focusHandler = e => {
|
||||
let { dispatch } = this.props
|
||||
|
||||
dispatch(searchArticle(''))
|
||||
}
|
||||
window.addEventListener('focus', this.focusHandler)
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('keydown', this.keyDownHandler)
|
||||
window.removeEventListener('focus', this.focusHandler)
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
@@ -59,17 +67,20 @@ class FinderMain extends React.Component {
|
||||
hideFinder()
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.keyCode === 91 || e.metaKey) {
|
||||
return
|
||||
}
|
||||
|
||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
||||
}
|
||||
|
||||
saveToClipboard () {
|
||||
let { activeArticle } = this.props
|
||||
clipboard.writeText(activeArticle.content)
|
||||
activityRecord.emit('FINDER_COPY')
|
||||
|
||||
notifier.notify({
|
||||
icon: getIconPath(),
|
||||
'title': 'Saved to Clipboard!',
|
||||
'message': 'Paste it wherever you want!'
|
||||
ipcRenderer.send('copy-finder')
|
||||
notify('Saved to Clipboard!', {
|
||||
body: 'Paste it wherever you want!'
|
||||
})
|
||||
hideFinder()
|
||||
}
|
||||
@@ -102,7 +113,7 @@ class FinderMain extends React.Component {
|
||||
let { articles, activeArticle, status, dispatch } = this.props
|
||||
let saveToClipboard = () => this.saveToClipboard()
|
||||
return (
|
||||
<div onClick={e => this.handleClick(e)} onKeyDown={e => this.handleKeyDown(e)} className='Finder'>
|
||||
<div onClick={e => this.handleClick(e)} className='Finder'>
|
||||
<FinderInput
|
||||
handleSearchChange={e => this.handleSearchChange(e)}
|
||||
ref='finderInput'
|
||||
@@ -156,6 +167,14 @@ function buildFilter (key) {
|
||||
return {type: TEXT_FILTER, value: key}
|
||||
}
|
||||
|
||||
function isContaining (target, needle) {
|
||||
return target.match(new RegExp(_.escapeRegExp(needle)))
|
||||
}
|
||||
|
||||
function startsWith (target, needle) {
|
||||
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
|
||||
}
|
||||
|
||||
function remap (state) {
|
||||
let { articles, folders, status } = state
|
||||
|
||||
@@ -172,10 +191,10 @@ function remap (state) {
|
||||
let targetFolders
|
||||
if (folders != null) {
|
||||
let exactTargetFolders = folders.filter(folder => {
|
||||
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
|
||||
return _.find(folderExactFilters, filter => isContaining(folder.name, filter.value))
|
||||
})
|
||||
let fuzzyTargetFolders = folders.filter(folder => {
|
||||
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
|
||||
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
|
||||
})
|
||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
||||
|
||||
@@ -188,7 +207,7 @@ function remap (state) {
|
||||
if (textFilters.length > 0) {
|
||||
articles = textFilters.reduce((articles, textFilter) => {
|
||||
return articles.filter(article => {
|
||||
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
|
||||
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
||||
})
|
||||
}, articles)
|
||||
}
|
||||
@@ -196,7 +215,7 @@ function remap (state) {
|
||||
if (tagFilters.length > 0) {
|
||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
||||
return articles.filter(article => {
|
||||
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
|
||||
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
||||
})
|
||||
}, articles)
|
||||
}
|
||||
@@ -205,7 +224,6 @@ function remap (state) {
|
||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
||||
if (activeArticle == null) activeArticle = articles[0]
|
||||
|
||||
console.log(status.search)
|
||||
return {
|
||||
articles,
|
||||
activeArticle,
|
||||
@@ -216,13 +234,19 @@ function remap (state) {
|
||||
var Finder = connect(remap)(FinderMain)
|
||||
var store = createStore(reducer)
|
||||
|
||||
function refreshData () {
|
||||
let data = dataStore.getData()
|
||||
store.dispatch(actions.refreshData(data))
|
||||
}
|
||||
|
||||
window.onfocus = e => {
|
||||
store.dispatch(refreshData())
|
||||
activityRecord.emit('FINDER_OPEN')
|
||||
refreshData()
|
||||
}
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<Finder/>
|
||||
</Provider>
|
||||
), document.getElementById('content'))
|
||||
), document.getElementById('content'), function () {
|
||||
refreshData()
|
||||
})
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { combineReducers } from 'redux'
|
||||
import { SELECT_ARTICLE, SEARCH_ARTICLE, REFRESH_DATA } from './actions'
|
||||
|
||||
let data = JSON.parse(localStorage.getItem('local'))
|
||||
|
||||
let initialArticles = data != null ? data.articles : []
|
||||
let initialFolders = data != null ? data.folders : []
|
||||
let initialArticles = []
|
||||
let initialFolders = []
|
||||
let initialStatus = {
|
||||
articleKey: null,
|
||||
search: ''
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React, { PropTypes} from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW, toggleTutorial } from 'boost/actions'
|
||||
// import UserNavigator from './HomePage/UserNavigator'
|
||||
import { EDIT_MODE, IDLE_MODE, toggleTutorial } from 'boost/actions'
|
||||
import ArticleNavigator from './HomePage/ArticleNavigator'
|
||||
import ArticleTopBar from './HomePage/ArticleTopBar'
|
||||
import ArticleList from './HomePage/ArticleList'
|
||||
import ArticleDetail from './HomePage/ArticleDetail'
|
||||
import _ from 'lodash'
|
||||
import keygen from 'boost/keygen'
|
||||
import { isModalOpen, closeModal } from 'boost/modal'
|
||||
|
||||
const electron = require('electron')
|
||||
const remote = electron.remote
|
||||
|
||||
const TEXT_FILTER = 'TEXT_FILTER'
|
||||
const FOLDER_FILTER = 'FOLDER_FILTER'
|
||||
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
|
||||
@@ -49,7 +50,7 @@ class HomePage extends React.Component {
|
||||
}
|
||||
|
||||
switch (status.mode) {
|
||||
case CREATE_MODE:
|
||||
|
||||
case EDIT_MODE:
|
||||
if (e.keyCode === 27) {
|
||||
detail.handleCancelButtonClick()
|
||||
@@ -57,6 +58,13 @@ class HomePage extends React.Component {
|
||||
if ((e.keyCode === 13 && (e.metaKey || e.ctrlKey)) || (e.keyCode === 83 && (e.metaKey || e.ctrlKey))) {
|
||||
detail.handleSaveButtonClick()
|
||||
}
|
||||
if (e.keyCode === 80 && e.metaKey) {
|
||||
detail.handleTogglePreviewButtonClick()
|
||||
}
|
||||
if (e.keyCode === 78 && e.metaKey) {
|
||||
nav.handleNewPostButtonClick()
|
||||
e.preventDefault()
|
||||
}
|
||||
break
|
||||
case IDLE_MODE:
|
||||
if (e.keyCode === 69) {
|
||||
@@ -91,7 +99,7 @@ class HomePage extends React.Component {
|
||||
list.selectNextArticle()
|
||||
}
|
||||
|
||||
if (e.keyCode === 65 || e.keyCode === 13 && (e.metaKey || e.ctrlKey)) {
|
||||
if ((e.keyCode === 65 && !e.metaKey && !e.ctrlKey) || (e.keyCode === 13 && e.metaKey) || (e.keyCode === 78 && e.metaKey)) {
|
||||
nav.handleNewPostButtonClick()
|
||||
e.preventDefault()
|
||||
}
|
||||
@@ -99,13 +107,14 @@ class HomePage extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { dispatch, status, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
||||
let { dispatch, status, user, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
||||
|
||||
return (
|
||||
<div className='HomePage'>
|
||||
<ArticleNavigator
|
||||
ref='nav'
|
||||
dispatch={dispatch}
|
||||
user={user}
|
||||
folders={folders}
|
||||
status={status}
|
||||
allArticles={allArticles}
|
||||
@@ -126,6 +135,7 @@ class HomePage extends React.Component {
|
||||
<ArticleDetail
|
||||
ref='detail'
|
||||
dispatch={dispatch}
|
||||
user={user}
|
||||
activeArticle={activeArticle}
|
||||
folders={folders}
|
||||
status={status}
|
||||
@@ -156,8 +166,16 @@ function buildFilter (key) {
|
||||
return {type: TEXT_FILTER, value: key}
|
||||
}
|
||||
|
||||
function isContaining (target, needle) {
|
||||
return target.match(new RegExp(_.escapeRegExp(needle)))
|
||||
}
|
||||
|
||||
function startsWith (target, needle) {
|
||||
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
|
||||
}
|
||||
|
||||
function remap (state) {
|
||||
let { folders, articles, status } = state
|
||||
let { user, folders, articles, status } = state
|
||||
|
||||
if (articles == null) articles = []
|
||||
articles.sort((a, b) => {
|
||||
@@ -184,10 +202,10 @@ function remap (state) {
|
||||
let targetFolders
|
||||
if (folders != null) {
|
||||
let exactTargetFolders = folders.filter(folder => {
|
||||
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
|
||||
return _.findWhere(folderExactFilters, {value: folder.name})
|
||||
})
|
||||
let fuzzyTargetFolders = folders.filter(folder => {
|
||||
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
|
||||
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
|
||||
})
|
||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
||||
|
||||
@@ -200,7 +218,7 @@ function remap (state) {
|
||||
if (textFilters.length > 0) {
|
||||
articles = textFilters.reduce((articles, textFilter) => {
|
||||
return articles.filter(article => {
|
||||
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
|
||||
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
||||
})
|
||||
}, articles)
|
||||
}
|
||||
@@ -208,7 +226,7 @@ function remap (state) {
|
||||
if (tagFilters.length > 0) {
|
||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
||||
return articles.filter(article => {
|
||||
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
|
||||
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
||||
})
|
||||
}, articles)
|
||||
}
|
||||
@@ -218,43 +236,8 @@ function remap (state) {
|
||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
||||
if (activeArticle == null) activeArticle = articles[0]
|
||||
|
||||
// remove Unsaved new article if user is not CREATE_MODE
|
||||
if (status.mode !== CREATE_MODE) {
|
||||
let targetIndex = _.findIndex(articles, article => article.status === NEW)
|
||||
|
||||
if (targetIndex >= 0) articles.splice(targetIndex, 1)
|
||||
}
|
||||
|
||||
// switching CREATE_MODE
|
||||
// restrict
|
||||
// 1. team have one folder at least
|
||||
// or Change IDLE MODE
|
||||
if (status.mode === CREATE_MODE) {
|
||||
let newArticle = _.findWhere(articles, {status: 'NEW'})
|
||||
console.log('targetFolders')
|
||||
let FolderKey = targetFolders.length > 0
|
||||
? targetFolders[0].key
|
||||
: folders[0].key
|
||||
|
||||
if (newArticle == null) {
|
||||
newArticle = {
|
||||
id: null,
|
||||
key: keygen(),
|
||||
title: '',
|
||||
content: '',
|
||||
mode: 'markdown',
|
||||
tags: [],
|
||||
FolderKey: FolderKey,
|
||||
status: NEW
|
||||
}
|
||||
articles.unshift(newArticle)
|
||||
}
|
||||
activeArticle = newArticle
|
||||
} else if (status.mode === CREATE_MODE) {
|
||||
status.mode = IDLE_MODE
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
folders,
|
||||
status,
|
||||
allArticles,
|
||||
@@ -270,11 +253,9 @@ function remap (state) {
|
||||
}
|
||||
|
||||
HomePage.propTypes = {
|
||||
params: PropTypes.shape({
|
||||
userId: PropTypes.string
|
||||
}),
|
||||
status: PropTypes.shape({
|
||||
userId: PropTypes.string
|
||||
status: PropTypes.shape(),
|
||||
user: PropTypes.shape({
|
||||
name: PropTypes.string
|
||||
}),
|
||||
articles: PropTypes.array,
|
||||
allArticles: PropTypes.array,
|
||||
@@ -285,7 +266,8 @@ HomePage.propTypes = {
|
||||
folder: PropTypes.array,
|
||||
tag: PropTypes.array,
|
||||
text: PropTypes.array
|
||||
})
|
||||
}),
|
||||
tags: PropTypes.array
|
||||
}
|
||||
|
||||
export default connect(remap)(HomePage)
|
||||
|
||||
151
browser/main/HomePage/ArticleDetail/ShareButton.js
Normal file
151
browser/main/HomePage/ArticleDetail/ShareButton.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import api from 'boost/api'
|
||||
import clientKey from 'boost/clientKey'
|
||||
import activityRecord from 'boost/activityRecord'
|
||||
const clipboard = require('electron').clipboard
|
||||
|
||||
function getDefault () {
|
||||
return {
|
||||
openDropdown: false,
|
||||
isSharing: false,
|
||||
// Fetched url
|
||||
url: null,
|
||||
// for tooltip Copy -> Copied!
|
||||
copied: false,
|
||||
failed: false
|
||||
}
|
||||
}
|
||||
|
||||
export default class ShareButton extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = getDefault()
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.setState(getDefault())
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.dropdownInterceptor = e => {
|
||||
this.dropdownClicked = true
|
||||
}
|
||||
ReactDOM.findDOMNode(this.refs.dropdown).addEventListener('click', this.dropdownInterceptor)
|
||||
this.shareViaPublicURLHandler = e => {
|
||||
this.handleShareViaPublicURLClick(e)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('click', this.dropdownHandler)
|
||||
ReactDOM.findDOMNode(this.refs.dropdown).removeEventListener('click', this.dropdownInterceptor)
|
||||
}
|
||||
|
||||
handleOpenButtonClick (e) {
|
||||
this.openDropdown()
|
||||
if (this.dropdownHandler == null) {
|
||||
this.dropdownHandler = e => {
|
||||
if (!this.dropdownClicked) {
|
||||
this.closeDropdown()
|
||||
} else {
|
||||
this.dropdownClicked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
document.removeEventListener('click', this.dropdownHandler)
|
||||
document.addEventListener('click', this.dropdownHandler)
|
||||
}
|
||||
|
||||
openDropdown () {
|
||||
this.setState({openDropdown: true})
|
||||
}
|
||||
|
||||
closeDropdown () {
|
||||
document.removeEventListener('click', this.dropdownHandler)
|
||||
this.setState({openDropdown: false})
|
||||
}
|
||||
|
||||
handleShareViaPublicURLClick (e) {
|
||||
let { user } = this.props
|
||||
let input = Object.assign({}, this.props.article, {
|
||||
clientKey: clientKey.get(),
|
||||
writerName: user.name
|
||||
})
|
||||
this.setState({
|
||||
isSharing: true,
|
||||
failed: false
|
||||
}, () => {
|
||||
api.shareViaPublicURL(input)
|
||||
.then(res => {
|
||||
let url = res.body.url
|
||||
this.setState({url: url, isSharing: false})
|
||||
activityRecord.emit('ARTICLE_SHARE')
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
this.setState({isSharing: false, failed: true})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handleCopyURLClick () {
|
||||
clipboard.writeText(this.state.url)
|
||||
this.setState({copied: true})
|
||||
}
|
||||
|
||||
// Restore copy url tooltip
|
||||
handleCopyURLMouseLeave () {
|
||||
this.setState({copied: false})
|
||||
}
|
||||
|
||||
render () {
|
||||
let hasPublicURL = this.state.url != null
|
||||
return (
|
||||
<div className='ShareButton'>
|
||||
<button ref='openButton' onClick={e => this.handleOpenButtonClick(e)} className='ShareButton-open-button'>
|
||||
<i className='fa fa-fw fa-share-alt'/>
|
||||
{
|
||||
this.state.openDropdown ? null : (
|
||||
<span className='tooltip'>Share</span>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
<div ref='dropdown' className={'share-dropdown' + (this.state.openDropdown ? '' : ' hide')}>
|
||||
{
|
||||
!hasPublicURL ? (
|
||||
<button
|
||||
onClick={e => this.shareViaPublicURLHandler(e)}
|
||||
ref='sharePublicURL'
|
||||
disabled={this.state.isSharing}>
|
||||
<i className='fa fa-fw fa-external-link'/> {this.state.failed ? 'Failed : Click to Try again' : !this.state.isSharing ? 'Share via public URL' : 'Sharing...'}
|
||||
</button>
|
||||
) : (
|
||||
<div className='ShareButton-url'>
|
||||
<input className='ShareButton-url-input' value={this.state.url} readOnly/>
|
||||
<button
|
||||
onClick={e => this.handleCopyURLClick(e)}
|
||||
className='ShareButton-url-button'
|
||||
onMouseLeave={e => this.handleCopyURLMouseLeave(e)}
|
||||
>
|
||||
<i className='fa fa-fw fa-clipboard'/>
|
||||
<div className='ShareButton-url-button-tooltip'>{this.state.copied ? 'Copied!' : 'Copy URL'}</div>
|
||||
</button>
|
||||
<div className='ShareButton-url-alert'>This url is valid for 7 days.</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ShareButton.propTypes = {
|
||||
article: PropTypes.shape({
|
||||
publicURL: PropTypes.string
|
||||
}),
|
||||
user: PropTypes.shape({
|
||||
name: PropTypes.string
|
||||
})
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import MarkdownPreview from 'boost/components/MarkdownPreview'
|
||||
import CodeEditor from 'boost/components/CodeEditor'
|
||||
import {
|
||||
IDLE_MODE,
|
||||
CREATE_MODE,
|
||||
EDIT_MODE,
|
||||
switchMode,
|
||||
switchArticle,
|
||||
@@ -25,6 +24,11 @@ import TagLink from 'boost/components/TagLink'
|
||||
import TagSelect from 'boost/components/TagSelect'
|
||||
import ModeSelect from 'boost/components/ModeSelect'
|
||||
import activityRecord from 'boost/activityRecord'
|
||||
import api from 'boost/api'
|
||||
import ShareButton from './ShareButton'
|
||||
|
||||
const electron = require('electron')
|
||||
const clipboard = electron.clipboard
|
||||
|
||||
const BRAND_COLOR = '#18AF90'
|
||||
|
||||
@@ -85,6 +89,10 @@ const modeSelectTutorialElement = (
|
||||
</svg>
|
||||
)
|
||||
|
||||
function notify (...args) {
|
||||
return new window.Notification(...args)
|
||||
}
|
||||
|
||||
function makeInstantArticle (article) {
|
||||
return Object.assign({}, article)
|
||||
}
|
||||
@@ -100,12 +108,16 @@ export default class ArticleDetail extends React.Component {
|
||||
isTagChanged: false,
|
||||
isTitleChanged: false,
|
||||
isContentChanged: false,
|
||||
isModeChanged: false
|
||||
isModeChanged: false,
|
||||
openShareDropdown: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
||||
this.shareDropdownInterceptor = e => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
@@ -114,7 +126,7 @@ export default class ArticleDetail extends React.Component {
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
let isModeChanged = prevProps.status.mode !== this.props.status.mode
|
||||
if (isModeChanged && this.props.status.mode !== IDLE_MODE) {
|
||||
if (isModeChanged && this.props.status.mode === EDIT_MODE) {
|
||||
ReactDOM.findDOMNode(this.refs.title).focus()
|
||||
}
|
||||
}
|
||||
@@ -124,6 +136,7 @@ export default class ArticleDetail extends React.Component {
|
||||
|
||||
let isArticleChanged = nextProps.activeArticle != null && (nextProps.activeArticle.key !== this.state.article.key)
|
||||
let isModeChanged = nextProps.status.mode !== this.props.status.mode
|
||||
|
||||
// Reset article input
|
||||
if (isArticleChanged || (isModeChanged && nextProps.status.mode !== IDLE_MODE)) {
|
||||
Object.assign(nextState, {
|
||||
@@ -154,6 +167,14 @@ export default class ArticleDetail extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
handleClipboardButtonClick (e) {
|
||||
activityRecord.emit('MAIN_DETAIL_COPY')
|
||||
clipboard.writeText(this.props.activeArticle.content)
|
||||
notify('Saved to Clipboard!', {
|
||||
body: 'Paste it wherever you want!'
|
||||
})
|
||||
}
|
||||
|
||||
handleEditButtonClick (e) {
|
||||
let { dispatch } = this.props
|
||||
dispatch(switchMode(EDIT_MODE))
|
||||
@@ -176,7 +197,7 @@ export default class ArticleDetail extends React.Component {
|
||||
}
|
||||
|
||||
renderIdle () {
|
||||
let { status, activeArticle, folders } = this.props
|
||||
let { status, activeArticle, folders, user } = this.props
|
||||
|
||||
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
|
||||
? activeArticle.tags.map(tag => {
|
||||
@@ -185,8 +206,13 @@ export default class ArticleDetail extends React.Component {
|
||||
: (
|
||||
<span className='noTags'>Not tagged yet</span>
|
||||
) : null
|
||||
|
||||
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
|
||||
|
||||
let title = activeArticle.title.trim().length === 0
|
||||
? <small>(Untitled)</small>
|
||||
: activeArticle.title
|
||||
|
||||
return (
|
||||
<div className='ArticleDetail idle'>
|
||||
{this.state.openDeleteConfirmMenu
|
||||
@@ -214,6 +240,15 @@ export default class ArticleDetail extends React.Component {
|
||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
||||
</div>
|
||||
<div className='right'>
|
||||
<ShareButton
|
||||
article={activeArticle}
|
||||
user={user}
|
||||
/>
|
||||
|
||||
<button onClick={e => this.handleClipboardButtonClick(e)} className='editBtn'>
|
||||
<i className='fa fa-fw fa-clipboard'/><span className='tooltip'>Copy to clipboard</span>
|
||||
</button>
|
||||
|
||||
<button onClick={e => this.handleEditButtonClick(e)} className='editBtn'>
|
||||
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
|
||||
</button>
|
||||
@@ -232,7 +267,7 @@ export default class ArticleDetail extends React.Component {
|
||||
<div className='detailPanel'>
|
||||
<div className='header'>
|
||||
<ModeIcon className='mode' mode={activeArticle.mode}/>
|
||||
<div className='title'>{activeArticle.title}</div>
|
||||
<div className='title'>{title}</div>
|
||||
</div>
|
||||
{activeArticle.mode === 'markdown'
|
||||
? <MarkdownPreview content={activeArticle.content}/>
|
||||
@@ -247,13 +282,14 @@ export default class ArticleDetail extends React.Component {
|
||||
handleCancelButtonClick (e) {
|
||||
let { activeArticle, dispatch } = this.props
|
||||
|
||||
dispatch(unlockStatus())
|
||||
if (activeArticle.status === NEW) dispatch(switchArticle(null))
|
||||
if (activeArticle.status === NEW) {
|
||||
dispatch(switchArticle(null))
|
||||
}
|
||||
dispatch(switchMode(IDLE_MODE))
|
||||
}
|
||||
|
||||
handleSaveButtonClick (e) {
|
||||
let { dispatch, folders, filters } = this.props
|
||||
let { dispatch, folders, status } = this.props
|
||||
let article = this.state.article
|
||||
let newArticle = Object.assign({}, article)
|
||||
|
||||
@@ -262,13 +298,17 @@ export default class ArticleDetail extends React.Component {
|
||||
|
||||
dispatch(unlockStatus())
|
||||
|
||||
delete newArticle.status
|
||||
newArticle.status = null
|
||||
newArticle.updatedAt = new Date()
|
||||
newArticle.title = newArticle.title.trim()
|
||||
if (newArticle.createdAt == null) {
|
||||
newArticle.createdAt = new Date()
|
||||
activityRecord.emit('ARTICLE_CREATE')
|
||||
if (newArticle.title.length === 0) {
|
||||
newArticle.title = `Created at ${moment(newArticle.createdAt).format('YYYY/MM/DD HH:mm')}`
|
||||
}
|
||||
activityRecord.emit('ARTICLE_CREATE', {mode: newArticle.mode})
|
||||
} else {
|
||||
activityRecord.emit('ARTICLE_UPDATE')
|
||||
activityRecord.emit('ARTICLE_UPDATE', {mode: newArticle.mode})
|
||||
}
|
||||
|
||||
dispatch(updateArticle(newArticle))
|
||||
@@ -277,7 +317,7 @@ export default class ArticleDetail extends React.Component {
|
||||
// Searchを初期化し、更新先のFolder filterをかける
|
||||
// かかれていない時に
|
||||
// Searchを初期化する
|
||||
if (filters.folder.length !== 0) dispatch(switchFolder(folder.name))
|
||||
if (status.targetFolders.length > 0) dispatch(switchFolder(folder.name))
|
||||
else dispatch(clearSearch())
|
||||
dispatch(switchArticle(newArticle.key))
|
||||
}
|
||||
@@ -319,8 +359,6 @@ export default class ArticleDetail extends React.Component {
|
||||
let article = this.state.article
|
||||
article.tags = tags
|
||||
|
||||
this.setState({article: article})
|
||||
|
||||
let _isTagChanged = _.difference(article.tags, this.props.activeArticle.tags).length > 0 || _.difference(this.props.activeArticle.tags, article.tags).length > 0
|
||||
|
||||
let { isTitleChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
|
||||
@@ -378,6 +416,11 @@ export default class ArticleDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleContentChange (e, value) {
|
||||
let { status } = this.props
|
||||
if (status.mode === IDLE_MODE) {
|
||||
return
|
||||
}
|
||||
|
||||
let { article } = this.state
|
||||
article.content = value
|
||||
let _isContentChanged = article.content !== this.props.activeArticle.content
|
||||
@@ -404,7 +447,36 @@ export default class ArticleDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleTogglePreviewButtonClick (e) {
|
||||
this.setState({previewMode: !this.state.previewMode})
|
||||
if (this.state.article.mode === 'markdown') {
|
||||
if (!this.state.previewMode) {
|
||||
let cursorPosition = this.refs.code.getCursorPosition()
|
||||
let firstVisibleRow = this.refs.code.getFirstVisibleRow()
|
||||
this.setState({
|
||||
previewMode: true,
|
||||
cursorPosition,
|
||||
firstVisibleRow
|
||||
}, function () {
|
||||
let previewEl = ReactDOM.findDOMNode(this.refs.preview)
|
||||
let anchors = previewEl.querySelectorAll('.lineAnchor')
|
||||
for (let i = 0; i < anchors.length; i++) {
|
||||
if (parseInt(anchors[i].dataset.key, 10) > cursorPosition.row || i === anchors.length - 1) {
|
||||
var targetAnchor = anchors[i > 0 ? i - 1 : 0]
|
||||
previewEl.scrollTop = targetAnchor.offsetTop - 100
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
previewMode: false
|
||||
}, function () {
|
||||
console.log(this.state.cursorPosition)
|
||||
this.refs.code.moveCursorTo(this.state.cursorPosition.row, this.state.cursorPosition.column)
|
||||
this.refs.code.scrollToLine(this.state.firstVisibleRow)
|
||||
this.refs.code.editor.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleTitleKeyDown (e) {
|
||||
@@ -449,18 +521,36 @@ export default class ArticleDetail extends React.Component {
|
||||
<div className='right'>
|
||||
{
|
||||
this.state.article.mode === 'markdown'
|
||||
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>{!this.state.previewMode ? 'Preview' : 'Edit'}</button>)
|
||||
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>
|
||||
{
|
||||
!this.state.previewMode
|
||||
? 'Preview'
|
||||
: 'Edit'
|
||||
}
|
||||
</button>)
|
||||
: null
|
||||
}
|
||||
<button onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
|
||||
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Save</button>
|
||||
<button onClick={e => this.handleCancelButtonClick(e)}>
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='detailBody'>
|
||||
<div className='detailPanel'>
|
||||
<div className='header'>
|
||||
<div className='title'>
|
||||
<input onKeyDown={e => this.handleTitleKeyDown(e)} placeholder='Title' ref='title' value={this.state.article.title} onChange={e => this.handleTitleChange(e)}/>
|
||||
<input
|
||||
onKeyDown={e => this.handleTitleKeyDown(e)}
|
||||
placeholder={this.state.article.createdAt == null
|
||||
? `Created at ${moment().format('YYYY/MM/DD HH:mm')}`
|
||||
: 'Title'}
|
||||
ref='title'
|
||||
value={this.state.article.title}
|
||||
onChange={e => this.handleTitleChange(e)}
|
||||
/>
|
||||
</div>
|
||||
<ModeSelect
|
||||
ref='mode'
|
||||
@@ -474,7 +564,7 @@ export default class ArticleDetail extends React.Component {
|
||||
</div>
|
||||
|
||||
{this.state.previewMode
|
||||
? <MarkdownPreview content={this.state.article.content}/>
|
||||
? <MarkdownPreview ref='preview' content={this.state.article.content}/>
|
||||
: (<CodeEditor
|
||||
ref='code'
|
||||
onChange={(e, value) => this.handleContentChange(e, value)}
|
||||
@@ -495,7 +585,6 @@ export default class ArticleDetail extends React.Component {
|
||||
if (activeArticle == null) return this.renderEmpty()
|
||||
|
||||
switch (status.mode) {
|
||||
case CREATE_MODE:
|
||||
case EDIT_MODE:
|
||||
return this.renderEdit()
|
||||
case IDLE_MODE:
|
||||
@@ -509,7 +598,8 @@ export default class ArticleDetail extends React.Component {
|
||||
ArticleDetail.propTypes = {
|
||||
status: PropTypes.shape(),
|
||||
activeArticle: PropTypes.shape(),
|
||||
activeUser: PropTypes.shape(),
|
||||
user: PropTypes.shape(),
|
||||
folders: PropTypes.array,
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
ArticleDetail.prototype.linkState = linkState
|
||||
@@ -80,6 +80,12 @@ export default class ArticleList extends React.Component {
|
||||
: (<span>Not tagged yet</span>)
|
||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
||||
|
||||
let title = article.status !== NEW
|
||||
? article.title.trim().length === 0
|
||||
? <small>(Untitled)</small>
|
||||
: article.title
|
||||
: '(New article)'
|
||||
|
||||
return (
|
||||
<div key={'article-' + article.key}>
|
||||
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
|
||||
@@ -91,7 +97,7 @@ export default class ArticleList extends React.Component {
|
||||
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
||||
</div>
|
||||
<div className='middle'>
|
||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{article.status !== NEW ? article.title : '(New article)'}</div>
|
||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{title}</div>
|
||||
</div>
|
||||
<div className='bottom'>
|
||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { findWhere } from 'lodash'
|
||||
import { setSearchFilter, switchFolder, switchMode, CREATE_MODE } from 'boost/actions'
|
||||
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, clearNewArticle, EDIT_MODE } from 'boost/actions'
|
||||
import { openModal } from 'boost/modal'
|
||||
import FolderMark from 'boost/components/FolderMark'
|
||||
import Preferences from 'boost/components/modal/Preferences'
|
||||
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
|
||||
|
||||
import remote from 'remote'
|
||||
let mainEnv = remote.getGlobal('process').env
|
||||
let userName = mainEnv.USER != null
|
||||
? mainEnv.USER
|
||||
: mainEnv.USERNAME
|
||||
import keygen from 'boost/keygen'
|
||||
|
||||
const BRAND_COLOR = '#18AF90'
|
||||
|
||||
@@ -68,9 +63,28 @@ export default class ArticleNavigator extends React.Component {
|
||||
}
|
||||
|
||||
handleNewPostButtonClick (e) {
|
||||
let { dispatch } = this.props
|
||||
let { dispatch, folders, status } = this.props
|
||||
let { targetFolders } = status
|
||||
|
||||
dispatch(switchMode(CREATE_MODE))
|
||||
let FolderKey = targetFolders.length > 0
|
||||
? targetFolders[0].key
|
||||
: folders[0].key
|
||||
|
||||
let newArticle = {
|
||||
id: null,
|
||||
key: keygen(),
|
||||
title: '',
|
||||
content: '',
|
||||
mode: 'markdown',
|
||||
tags: [],
|
||||
FolderKey: FolderKey,
|
||||
status: 'NEW'
|
||||
}
|
||||
|
||||
dispatch(clearNewArticle())
|
||||
dispatch(updateArticle(newArticle))
|
||||
dispatch(switchArticle(newArticle.key, true))
|
||||
dispatch(switchMode(EDIT_MODE))
|
||||
}
|
||||
|
||||
handleNewFolderButton (e) {
|
||||
@@ -91,13 +105,13 @@ export default class ArticleNavigator extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { status, folders, allArticles } = this.props
|
||||
let { status, user, folders, allArticles } = this.props
|
||||
let { targetFolders } = status
|
||||
if (targetFolders == null) targetFolders = []
|
||||
|
||||
let folderElememts = folders.map((folder, index) => {
|
||||
let isActive = findWhere(targetFolders, {key: folder.key})
|
||||
let articleCount = allArticles.filter(article => article.FolderKey === folder.key).length
|
||||
let articleCount = allArticles.filter(article => article.FolderKey === folder.key && article.status !== 'NEW').length
|
||||
|
||||
return (
|
||||
<button onClick={e => this.handleFolderButtonClick(folder.name)(e)} key={'folder-' + folder.key} className={isActive ? 'active' : ''}>
|
||||
@@ -109,7 +123,7 @@ export default class ArticleNavigator extends React.Component {
|
||||
return (
|
||||
<div className='ArticleNavigator'>
|
||||
<div className='userInfo'>
|
||||
<div className='userProfileName'>{userName}</div>
|
||||
<div className='userProfileName'>{user.name}</div>
|
||||
<div className='userName'>localStorage</div>
|
||||
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
|
||||
<i className='fa fa-fw fa-chevron-down'/>
|
||||
|
||||
@@ -35,18 +35,33 @@ export default class ArticleTopBar extends React.Component {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isTooltipHidden: true
|
||||
isTooltipHidden: true,
|
||||
isLinksDropdownOpen: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.searchInput = ReactDOM.findDOMNode(this.refs.searchInput)
|
||||
this.linksButton = ReactDOM.findDOMNode(this.refs.links)
|
||||
this.showLinksDropdown = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (!this.state.isLinksDropdownOpen) {
|
||||
this.setState({isLinksDropdownOpen: true})
|
||||
}
|
||||
}
|
||||
this.linksButton.addEventListener('click', this.showLinksDropdown)
|
||||
this.hideLinksDropdown = e => {
|
||||
if (this.state.isLinksDropdownOpen) {
|
||||
this.setState({isLinksDropdownOpen: false})
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', this.hideLinksDropdown)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.searchInput.removeEventListener('keydown', this.showTooltip)
|
||||
this.searchInput.removeEventListener('focus', this.showTooltip)
|
||||
this.searchInput.removeEventListener('blur', this.showTooltip)
|
||||
document.removeEventListener('click', this.hideLinksDropdown)
|
||||
this.linksButton.removeEventListener('click', this.showLinksDropdown())
|
||||
}
|
||||
|
||||
handleTooltipRequest (e) {
|
||||
@@ -118,8 +133,11 @@ export default class ArticleTopBar extends React.Component {
|
||||
: null
|
||||
}
|
||||
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
|
||||
- Search by tag : #{'{string}'}<br/>
|
||||
- Search by folder : /{'{folder_name}'}
|
||||
<ul>
|
||||
<li>- Search by tag : #{'{string}'}</li>
|
||||
<li>- Search by folder : /{'{folder_name}'}</li>
|
||||
<li><small>exact match : //{'{folder_name}'}</small></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -129,10 +147,23 @@ export default class ArticleTopBar extends React.Component {
|
||||
<div className='right'>
|
||||
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
|
||||
</button>
|
||||
<ExternalLink className='logo' href='http://b00st.io'>
|
||||
<a ref='links' className='linksBtn' href>
|
||||
<img src='../../resources/favicon-230x230.png' width='44' height='44'/>
|
||||
<span className='tooltip'>Boost official page</span>
|
||||
</ExternalLink>
|
||||
</a>
|
||||
{
|
||||
this.state.isLinksDropdownOpen
|
||||
? (
|
||||
<div className='links-dropdown'>
|
||||
<ExternalLink className='links-item' href='https://b00st.io'>
|
||||
<i className='fa fa-fw fa-home'/>Boost official page
|
||||
</ExternalLink>
|
||||
<ExternalLink className='links-item' href='https://github.com/BoostIO/boost-app-discussions/issues'>
|
||||
<i className='fa fa-fw fa-bullhorn'/> Discuss
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
|
||||
{status.isTutorialOpen ? (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import ipc from 'ipc'
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
var ContactModal = require('boost/components/modal/ContactModal')
|
||||
|
||||
export default class MainContainer extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
@@ -19,20 +18,12 @@ export default class MainContainer extends React.Component {
|
||||
ipc.send('update-app', 'Deal with it.')
|
||||
}
|
||||
|
||||
openContactModal () {
|
||||
this.openModal(ContactModal)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='Main'>
|
||||
{this.state.updateAvailable ? (
|
||||
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
|
||||
) : null}
|
||||
{/* <button onClick={this.openContactModal} className='contactButton'>
|
||||
<i className='fa fa-paper-plane-o'/>
|
||||
<div className='tooltip'>Contact us</div>
|
||||
</button> */}
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
||||
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
||||
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
|
||||
<style>
|
||||
@@ -53,8 +54,9 @@
|
||||
|
||||
<script src="../../submodules/ace/src-min/ace.js"></script>
|
||||
<script type='text/javascript'>
|
||||
require('web-frame').setZoomLevelLimits(1, 1)
|
||||
var version = require('remote').require('app').getVersion()
|
||||
const electron = require('electron')
|
||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
||||
var version = electron.remote.app.getVersion()
|
||||
document.title = 'Boost' + ((version == null || version.length === 0) ? ' DEV' : '')
|
||||
var scriptUrl = process.env.BOOST_ENV === 'development'
|
||||
? 'http://localhost:8080/assets/main.js'
|
||||
|
||||
@@ -11,13 +11,31 @@ require('../styles/main/index.styl')
|
||||
import { openModal } from 'boost/modal'
|
||||
import Tutorial from 'boost/components/modal/Tutorial'
|
||||
import activityRecord from 'boost/activityRecord'
|
||||
import ipc from 'ipc'
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
activityRecord.init()
|
||||
window.addEventListener('online', function () {
|
||||
ipc.send('check-update', 'check-update')
|
||||
})
|
||||
|
||||
function notify (...args) {
|
||||
return new window.Notification(...args)
|
||||
}
|
||||
|
||||
ipc.on('notify', function (e, payload) {
|
||||
notify(payload.title, {
|
||||
body: payload.body
|
||||
})
|
||||
})
|
||||
|
||||
ipc.on('copy-finder', function () {
|
||||
activityRecord.emit('FINDER_COPY')
|
||||
})
|
||||
ipc.on('open-finder', function () {
|
||||
activityRecord.emit('FINDER_OPEN')
|
||||
})
|
||||
|
||||
let routes = (
|
||||
<Route path='/' component={MainPage}>
|
||||
<IndexRoute name='home' component={HomePage}/>
|
||||
|
||||
@@ -16,6 +16,8 @@ body
|
||||
width 100%
|
||||
height 100%
|
||||
overflow hidden
|
||||
button, input
|
||||
font-family "Lato"
|
||||
|
||||
.Finder
|
||||
absolute top bottom left right
|
||||
|
||||
@@ -284,7 +284,87 @@ iptFocusBorderColor = #369DCD
|
||||
color noTagsColor
|
||||
.right
|
||||
z-index 30
|
||||
button
|
||||
div.share-dropdown
|
||||
position absolute
|
||||
right 5px
|
||||
top 30px
|
||||
background-color transparentify(invBackgroundColor, 80%)
|
||||
padding 5px 0
|
||||
width 200px
|
||||
&.hide
|
||||
display none
|
||||
&>button
|
||||
width 200px
|
||||
text-align left
|
||||
display block
|
||||
height 33px
|
||||
background-color transparent
|
||||
color white
|
||||
font-size 14px
|
||||
padding 0 10px
|
||||
border none
|
||||
&:hover
|
||||
background-color transparentify(lighten(invBackgroundColor, 30%), 80%)
|
||||
&>.ShareButton-url
|
||||
clearfix()
|
||||
input.ShareButton-url-input
|
||||
width 155px
|
||||
margin 0 0 0 5px
|
||||
height 25px
|
||||
outline none
|
||||
border none
|
||||
border-top-left-radius 5px
|
||||
border-bottom-left-radius 5px
|
||||
float left
|
||||
padding 5px
|
||||
button.ShareButton-url-button
|
||||
width 35px
|
||||
height 25px
|
||||
border none
|
||||
margin 0 5px 0 0
|
||||
outline none
|
||||
border-top-right-radius 5px
|
||||
border-bottom-right-radius 5px
|
||||
background-color darken(white, 5%)
|
||||
color inactiveTextColor
|
||||
float right
|
||||
div.ShareButton-url-button-tooltip
|
||||
tooltip()
|
||||
right 10px
|
||||
&:hover
|
||||
color textColor
|
||||
div.ShareButton-url-button-tooltip
|
||||
opacity 1
|
||||
div.ShareButton-url-alert
|
||||
float left
|
||||
height 25px
|
||||
line-height 25px
|
||||
padding 0 15px
|
||||
color white
|
||||
|
||||
.ShareButton
|
||||
display inline-block
|
||||
button.ShareButton-open-button
|
||||
border-radius 16.5px
|
||||
cursor pointer
|
||||
height 33px
|
||||
width 33px
|
||||
border none
|
||||
margin-right 5px
|
||||
font-size 18px
|
||||
color inactiveTextColor
|
||||
background-color darken(white, 5%)
|
||||
padding 0
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top 25px
|
||||
margin-left -40px
|
||||
&:hover
|
||||
color textColor
|
||||
.tooltip
|
||||
opacity 1
|
||||
|
||||
&>button
|
||||
border-radius 16.5px
|
||||
cursor pointer
|
||||
height 33px
|
||||
@@ -323,7 +403,8 @@ iptFocusBorderColor = #369DCD
|
||||
right 15px
|
||||
font-size 24px
|
||||
line-height 60px
|
||||
|
||||
white-space nowrap
|
||||
overflow-x auto
|
||||
overflow-y hidden
|
||||
small
|
||||
color #AAA
|
||||
|
||||
@@ -48,6 +48,8 @@ articleItemColor = #777
|
||||
left 19px
|
||||
right 0
|
||||
overflow ellipsis
|
||||
small
|
||||
color #AAA
|
||||
.bottom
|
||||
padding 5px 0
|
||||
overflow-x auto
|
||||
|
||||
@@ -14,7 +14,7 @@ articleCount = #999
|
||||
.userProfileName
|
||||
color brandColor
|
||||
font-size 28px
|
||||
padding 6px 0 0 10px
|
||||
padding 6px 37px 0 10px
|
||||
white-space nowrap
|
||||
text-overflow ellipsis
|
||||
overflow hidden
|
||||
|
||||
@@ -62,6 +62,13 @@ infoBtnActiveBgColor = #3A3A3A
|
||||
opacity 1
|
||||
&.hide
|
||||
opacity 0
|
||||
ul
|
||||
li:last-child
|
||||
line-height 10px
|
||||
margin-bottom 3px
|
||||
small
|
||||
font-size 10px
|
||||
margin-left 15px
|
||||
input
|
||||
absolute top left
|
||||
width 350px
|
||||
@@ -140,17 +147,33 @@ infoBtnActiveBgColor = #3A3A3A
|
||||
.tooltip
|
||||
opacity 1
|
||||
|
||||
&>.logo
|
||||
&>.linksBtn
|
||||
display block
|
||||
position absolute
|
||||
top 8px
|
||||
right 15px
|
||||
opacity 0.7
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top 44px
|
||||
margin-left -120px
|
||||
&:hover
|
||||
opacity 1
|
||||
.tooltip
|
||||
opacity 1
|
||||
&>.links-dropdown
|
||||
position fixed
|
||||
z-index 50
|
||||
right 10px
|
||||
top 40px
|
||||
background-color transparentify(invBackgroundColor, 80%)
|
||||
padding 5px 0
|
||||
.links-item
|
||||
padding 0 10px
|
||||
height 33px
|
||||
width 100%
|
||||
display block
|
||||
line-height 33px
|
||||
text-decoration none
|
||||
color white
|
||||
&:hover
|
||||
background-color transparentify(lighten(invBackgroundColor, 30%), 80%)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
marked()
|
||||
h1, h2, h3, h4, h5, h6, p
|
||||
&:first-child
|
||||
margin-top 0
|
||||
hr
|
||||
border-top none
|
||||
border-bottom solid 1px borderColor
|
||||
margin 15px 0
|
||||
h1, h2, h3, h4, h5, h6
|
||||
margin 0 0 15px
|
||||
font-weight 600
|
||||
* + h1, * + h2, * + h3, * + h4, * + h5, * + h6
|
||||
margin-top 25px
|
||||
h1
|
||||
font-size 2em
|
||||
border-bottom solid 2px borderColor
|
||||
margin 0.33em auto 0.67em
|
||||
line-height 2.333em
|
||||
h2
|
||||
font-size 1.5em
|
||||
margin 0.42em auto 0.83em
|
||||
font-size 1.66em
|
||||
line-height 2.07em
|
||||
h3
|
||||
font-size 1.17em
|
||||
margin 0.5em auto 1em
|
||||
font-size 1.33em
|
||||
line-height 1.6625em
|
||||
h4
|
||||
font-size 1em
|
||||
margin 0.67em auto 1.33em
|
||||
font-size 1.15em
|
||||
line-height 1.4375em
|
||||
h5
|
||||
font-size 0.83em
|
||||
margin 0.84em auto 1.67em
|
||||
font-size 1em
|
||||
line-height 1.25em
|
||||
h6
|
||||
font-size 0.67em
|
||||
margin 1.16em auto 2.33em
|
||||
h1, h2, h3, h4, h5, h6
|
||||
font-weight 700
|
||||
line-height 1.8em
|
||||
font-size 0.8em
|
||||
line-height 1em
|
||||
|
||||
* + p, * + blockquote, * + ul, * + ol, * + pre
|
||||
margin-top 15px
|
||||
p
|
||||
line-height 1.8em
|
||||
margin 15px 0 25px
|
||||
line-height 1.9em
|
||||
margin 0 0 15px
|
||||
img
|
||||
max-width 100%
|
||||
strong
|
||||
@@ -41,15 +43,17 @@ marked()
|
||||
text-decoration line-through
|
||||
blockquote
|
||||
border-left solid 4px brandBorderColor
|
||||
margin 15px 0 25px
|
||||
margin 0 0 15px
|
||||
padding 0 25px
|
||||
ul
|
||||
list-style-type disc
|
||||
padding-left 35px
|
||||
margin-bottom 35px
|
||||
margin-bottom 15px
|
||||
li
|
||||
display list-item
|
||||
line-height 1.8em
|
||||
&>li>ul, &>li>ol
|
||||
margin 0
|
||||
&>li>ul
|
||||
list-style-type circle
|
||||
&>li>ul
|
||||
@@ -57,12 +61,14 @@ marked()
|
||||
ol
|
||||
list-style-type decimal
|
||||
padding-left 35px
|
||||
margin-bottom 35px
|
||||
margin-bottom 15px
|
||||
li
|
||||
display list-item
|
||||
line-height 1.8em
|
||||
&>li>ul, &>li>ol
|
||||
margin 0
|
||||
code
|
||||
font-family monospace
|
||||
font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
|
||||
padding 2px 4px
|
||||
border solid 1px borderColor
|
||||
border-radius 4px
|
||||
@@ -70,14 +76,19 @@ marked()
|
||||
color black
|
||||
text-decoration none
|
||||
background-color #F6F6F6
|
||||
margin-right 2px
|
||||
* + code
|
||||
margin-left 2px
|
||||
pre
|
||||
padding 5px
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
overflow-x auto
|
||||
margin 15px 0 25px
|
||||
margin 0 0 15px
|
||||
background-color #F6F6F6
|
||||
line-height 1.35em
|
||||
&>code
|
||||
margin 0
|
||||
padding 0
|
||||
border none
|
||||
border-radius 0
|
||||
|
||||
Reference in New Issue
Block a user