mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +00:00
added snippet config in setting
This commit is contained in:
@@ -96,18 +96,25 @@ export default class CodeEditor extends React.Component {
|
||||
const { rulers, enableRulers } = this.props
|
||||
const storagePath = findStorage(this.props.storageKey).path
|
||||
const expandDataFile = path.join(storagePath, 'expandData.json')
|
||||
const emptyChars = /\t|\s|\r|\n/
|
||||
if (!fs.existsSync(expandDataFile)) {
|
||||
const defaultExpandData = [
|
||||
{
|
||||
matches: ['expandme'],
|
||||
content: 'Here I am'
|
||||
}
|
||||
matches: ['lorem', 'ipsum'],
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||
},
|
||||
{ match: 'h1', content: '# '},
|
||||
{ match: 'h2', content: '## '},
|
||||
{ match: 'h3', content: '### '},
|
||||
{ match: 'h4', content: '#### '},
|
||||
{ match: 'h5', content: '##### '},
|
||||
{ match: 'h6', content: '###### '}
|
||||
];
|
||||
fs.writeFileSync(expandDataFile, JSON.stringify(defaultExpandData), 'utf8')
|
||||
}
|
||||
const expandData = JSON.parse(fs.readFileSync(expandDataFile, 'utf8'))
|
||||
const expandSnippet = this.expandSnippet.bind(this)
|
||||
this.value = this.props.value
|
||||
|
||||
this.editor = CodeMirror(this.refs.root, {
|
||||
rulers: buildCMRulers(rulers, enableRulers),
|
||||
value: this.props.value,
|
||||
@@ -141,64 +148,14 @@ export default class CodeEditor extends React.Component {
|
||||
cm.execCommand('insertSoftTab')
|
||||
}
|
||||
cm.execCommand('goLineEnd')
|
||||
} else if (charBeforeCursor !== ' ') {
|
||||
// text expansion on tab key
|
||||
let tempCursorPosition = cursorPosition;
|
||||
let wordBeforeCursor = '';
|
||||
const emptyChars = /\t|\s|\r|\n/
|
||||
|
||||
// to prevent the word to expand is long that will crash the whole app
|
||||
// the safeStop is there to stop user to expand words that longer than 20 chars
|
||||
const safeStop = 20;
|
||||
|
||||
while (tempCursorPosition > 0) {
|
||||
let currentChar = line.substr(tempCursorPosition - 1, 1);
|
||||
// if char is not an empty char
|
||||
if (!emptyChars.test(currentChar)) {
|
||||
wordBeforeCursor = currentChar + wordBeforeCursor
|
||||
} else if (wordBeforeCursor.length >= safeStop) {
|
||||
throw new Error("Your text expansion word is too long !")
|
||||
} else if (!emptyChars.test(charBeforeCursor) || cursor.ch !== 0) {
|
||||
// text expansion on tab key if the char before is alphabet
|
||||
if (expandSnippet(line, cursor, cm, expandData) === false) {
|
||||
if (tabs) {
|
||||
cm.execCommand('insertTab')
|
||||
} else {
|
||||
break
|
||||
cm.execCommand('insertSoftTab')
|
||||
}
|
||||
tempCursorPosition--;
|
||||
}
|
||||
|
||||
for (let i = 0; i < expandData.length; i++) {
|
||||
if (Array.isArray(expandData[i].matches)) {
|
||||
if (expandData[i].matches.indexOf(wordBeforeCursor) !== -1) {
|
||||
let range = {
|
||||
from: {line: cursor.line, ch: cursor.ch},
|
||||
to: {line: cursor.line, ch: tempCursorPosition}
|
||||
}
|
||||
cm.replaceRange(
|
||||
expandData[i].content,
|
||||
range.from,
|
||||
range.to
|
||||
)
|
||||
return // stop if text is expanded
|
||||
}
|
||||
}
|
||||
else if (typeof(expandData[i].matches) === 'string') {
|
||||
if (expandData[i].matches === wordBeforeCursor) {
|
||||
let range = {
|
||||
from: {line: cursor.line, ch: cursor.ch},
|
||||
to: {line: cursor.line, ch: tempCursorPosition}
|
||||
}
|
||||
cm.replaceRange(
|
||||
expandData[i].content,
|
||||
range.from,
|
||||
range.to
|
||||
)
|
||||
return // stop if text is expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tabs) {
|
||||
cm.execCommand('insertTab')
|
||||
} else {
|
||||
cm.execCommand('insertSoftTab')
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -244,6 +201,65 @@ export default class CodeEditor extends React.Component {
|
||||
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||
}
|
||||
|
||||
expandSnippet(line, cursor, cm, expandData) {
|
||||
let wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
|
||||
for (let i = 0; i < expandData.length; i++) {
|
||||
if (Array.isArray(expandData[i].matches)) {
|
||||
if (expandData[i].matches.indexOf(wordBeforeCursor.text) !== -1) {
|
||||
cm.replaceRange(
|
||||
expandData[i].content,
|
||||
wordBeforeCursor.range.from,
|
||||
wordBeforeCursor.range.to
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
else if (typeof(expandData[i].matches) === 'string') {
|
||||
if (expandData[i].match === wordBeforeCursor.text) {
|
||||
cm.replaceRange(
|
||||
expandData[i].content,
|
||||
wordBeforeCursor.range.from,
|
||||
wordBeforeCursor.range.to
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
getWordBeforeCursor(line, lineNumber, cursorPosition) {
|
||||
let wordBeforeCursor = ''
|
||||
let originCursorPosition = cursorPosition
|
||||
const emptyChars = /\t|\s|\r|\n/
|
||||
|
||||
// to prevent the word to expand is long that will crash the whole app
|
||||
// the safeStop is there to stop user to expand words that longer than 20 chars
|
||||
const safeStop = 20
|
||||
|
||||
while (cursorPosition > 0) {
|
||||
let currentChar = line.substr(cursorPosition - 1, 1)
|
||||
// if char is not an empty char
|
||||
if (!emptyChars.test(currentChar)) {
|
||||
wordBeforeCursor = currentChar + wordBeforeCursor
|
||||
} else if (wordBeforeCursor.length >= safeStop) {
|
||||
throw new Error("Your text expansion word is too long !")
|
||||
} else {
|
||||
break
|
||||
}
|
||||
cursorPosition--;
|
||||
}
|
||||
|
||||
return {
|
||||
text: wordBeforeCursor,
|
||||
range: {
|
||||
from: {line: lineNumber, ch: originCursorPosition},
|
||||
to: {line: lineNumber, ch: cursorPosition}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quitEditor () {
|
||||
document.querySelector('textarea').blur()
|
||||
}
|
||||
|
||||
66
browser/main/modals/PreferencesModal/SnippetEditor.js
Normal file
66
browser/main/modals/PreferencesModal/SnippetEditor.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import CodeMirror from 'codemirror'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||
const buildCMRulers = (rulers, enableRulers) =>
|
||||
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
|
||||
|
||||
export default class SnippetEditor extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { rulers, enableRulers } = this.props
|
||||
let cm = CodeMirror(this.refs.root, {
|
||||
rulers: buildCMRulers(rulers, enableRulers),
|
||||
lineNumbers: this.props.displayLineNumbers,
|
||||
lineWrapping: true,
|
||||
theme: this.props.theme,
|
||||
indentUnit: this.props.indentSize,
|
||||
tabSize: this.props.indentSize,
|
||||
indentWithTabs: this.props.indentType !== 'space',
|
||||
keyMap: this.props.keyMap,
|
||||
scrollPastEnd: this.props.scrollPastEnd,
|
||||
dragDrop: false,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
autoCloseBrackets: true,
|
||||
})
|
||||
cm.setValue("asdasd")
|
||||
cm.setSize("100%", "100%")
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fontSize } = this.props
|
||||
let fontFamily = this.props.fontFamily
|
||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||
? [fontFamily].concat(defaultEditorFontFamily)
|
||||
: defaultEditorFontFamily
|
||||
return (
|
||||
<div
|
||||
styleName="SnippetEditor"
|
||||
ref='root'
|
||||
tabIndex='-1'
|
||||
style={{
|
||||
fontFamily: defaultEditorFontFamily.join(', '),
|
||||
fontSize: fontSize,
|
||||
position: 'relative',
|
||||
height: 'calc(100vh - 310px)'
|
||||
}}>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SnippetEditor.defaultProps = {
|
||||
readOnly: false,
|
||||
theme: 'xcode',
|
||||
keyMap: 'sublime',
|
||||
fontSize: 14,
|
||||
fontFamily: 'Monaco, Consolas',
|
||||
indentSize: 4,
|
||||
indentType: 'space'
|
||||
}
|
||||
74
browser/main/modals/PreferencesModal/SnippetTab.js
Normal file
74
browser/main/modals/PreferencesModal/SnippetTab.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './SnippetTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import SnippetEditor from './SnippetEditor';
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class SnippetTab extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
snippets: [
|
||||
{ id: 'abcsajisdjiasd', name: 'Hello' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
renderSnippetList () {
|
||||
let { snippets } = this.state
|
||||
return (
|
||||
snippets.map((snippet) => (
|
||||
<div styleName='snippet-item' key={snippet.id}>
|
||||
{snippet.name}
|
||||
</div>
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { config } = this.props
|
||||
|
||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='header'>{i18n.__('Snippets')}</div>
|
||||
<div styleName='snippet-list'>
|
||||
{this.renderSnippetList()}
|
||||
</div>
|
||||
<div styleName='snippet-detail'>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input' type='text' />
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='snippet-editor-section'>
|
||||
<SnippetEditor
|
||||
theme={config.editor.theme}
|
||||
keyMap={config.editor.keyMap}
|
||||
fontFamily={config.editor.fontFamily}
|
||||
fontSize={editorFontSize}
|
||||
indentType={config.editor.indentType}
|
||||
indentSize={editorIndentSize}
|
||||
enableRulers={config.editor.enableRulers}
|
||||
rulers={config.editor.rulers}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
scrollPastEnd={config.editor.scrollPastEnd} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SnippetTab.PropTypes = {
|
||||
}
|
||||
|
||||
export default CSSModules(SnippetTab, styles)
|
||||
106
browser/main/modals/PreferencesModal/SnippetTab.styl
Normal file
106
browser/main/modals/PreferencesModal/SnippetTab.styl
Normal file
@@ -0,0 +1,106 @@
|
||||
@import('./Tab')
|
||||
|
||||
.root
|
||||
padding 15px
|
||||
white-space pre
|
||||
line-height 1.4
|
||||
color alpha($ui-text-color, 90%)
|
||||
width 100%
|
||||
font-size 14px
|
||||
|
||||
.group
|
||||
margin-bottom 45px
|
||||
|
||||
.group-header
|
||||
@extend .header
|
||||
color $ui-text-color
|
||||
|
||||
.group-header2
|
||||
font-size 20px
|
||||
color $ui-text-color
|
||||
margin-bottom 15px
|
||||
margin-top 30px
|
||||
|
||||
.group-section
|
||||
margin-bottom 20px
|
||||
display flex
|
||||
line-height 30px
|
||||
|
||||
.group-section-label
|
||||
width 150px
|
||||
text-align left
|
||||
margin-right 10px
|
||||
font-size 14px
|
||||
|
||||
.group-section-control
|
||||
flex 1
|
||||
margin-left 5px
|
||||
|
||||
.group-section-control select
|
||||
outline none
|
||||
border 1px solid $ui-borderColor
|
||||
font-size 16px
|
||||
height 30px
|
||||
width 250px
|
||||
margin-bottom 5px
|
||||
background-color transparent
|
||||
|
||||
.group-section-control-input
|
||||
height 30px
|
||||
vertical-align middle
|
||||
width 400px
|
||||
font-size $tab--button-font-size
|
||||
border solid 1px $border-color
|
||||
border-radius 2px
|
||||
padding 0 5px
|
||||
outline none
|
||||
&:disabled
|
||||
background-color $ui-input--disabled-backgroundColor
|
||||
|
||||
.group-checkBoxSection
|
||||
margin-bottom 15px
|
||||
display flex
|
||||
line-height 30px
|
||||
padding-left 15px
|
||||
|
||||
.group-control
|
||||
padding-top 10px
|
||||
box-sizing border-box
|
||||
height 40px
|
||||
text-align right
|
||||
:global
|
||||
.alert
|
||||
display inline-block
|
||||
position absolute
|
||||
top 60px
|
||||
right 15px
|
||||
font-size 14px
|
||||
.success
|
||||
color #1EC38B
|
||||
.error
|
||||
color red
|
||||
.warning
|
||||
color #FFA500
|
||||
|
||||
.snippet-list
|
||||
width 30%
|
||||
height calc(100% - 200px)
|
||||
background #f5f5f5
|
||||
position absolute
|
||||
|
||||
.snippet-item
|
||||
width 100%
|
||||
height 50px
|
||||
font-size 20px
|
||||
line-height 50px
|
||||
padding 0 10px
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
background darken(#f5f5f5, 5)
|
||||
|
||||
.snippet-detail
|
||||
width 70%
|
||||
height calc(100% - 200px)
|
||||
position absolute
|
||||
left 33%
|
||||
@@ -6,6 +6,7 @@ import UiTab from './UiTab'
|
||||
import InfoTab from './InfoTab'
|
||||
import Crowdfunding from './Crowdfunding'
|
||||
import StoragesTab from './StoragesTab'
|
||||
import SnippetTab from './SnippetTab'
|
||||
import Blog from './Blog'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
@@ -86,6 +87,14 @@ class Preferences extends React.Component {
|
||||
haveToSave={alert => this.setState({BlogAlert: alert})}
|
||||
/>
|
||||
)
|
||||
case 'SNIPPET':
|
||||
return (
|
||||
<SnippetTab
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
case 'STORAGES':
|
||||
default:
|
||||
return (
|
||||
@@ -123,7 +132,8 @@ class Preferences extends React.Component {
|
||||
{target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert},
|
||||
{target: 'INFO', label: i18n.__('About')},
|
||||
{target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')},
|
||||
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}
|
||||
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert},
|
||||
{target: 'SNIPPET', label: i18n.__('Snippets')}
|
||||
]
|
||||
|
||||
const navButtons = tabs.map((tab) => {
|
||||
|
||||
Reference in New Issue
Block a user