renderList () { const { data, boundingBox } = this.props if (!boundingBox) { return null } const storageList = data.storageMap.map((storage) => { return <StorageItem key={storage.key} storage={storage} hostBoundingBox={boundingBox} /> }) return ( <div styleName='list'> <div styleName='header'>{i18n.__('Storages')}</div> {storageList.length > 0 ? storageList : <div styleName='list-empty'>{i18n.__('No storage found.')}</div> } <div styleName='list-control'> <button styleName='list-control-addStorageButton' onClick={(e) => this.handleAddStorageButton(e)} > <i className='fa fa-plus' /> {i18n.__('Add Storage Location')} </button> </div> </div> ) }
showWarning () { dialog.showMessageBox(remote.getCurrentWindow(), { type: 'warning', message: i18n.__('Sorry!'), detail: i18n.__('md/text import is available only a markdown note.'), buttons: [i18n.__('OK')] }) }
alertIfSnippet () { const targetIndex = this.getTargetIndex() if (this.notes[targetIndex].type === 'SNIPPET_NOTE') { dialog.showMessageBox(remote.getCurrentWindow(), { type: 'warning', message: i18n.__('Sorry!'), detail: i18n.__('md/text import is available only a markdown note.'), buttons: [i18n.__('OK'), i18n.__('Cancel')] }) } }
confirmPublishError () { const { remote } = electron const { dialog } = remote const alertError = { type: 'warning', message: i18n.__('Publish Failed'), detail: i18n.__('Check and update your blog setting and try again.'), buttons: [i18n.__('Confirm')] } dialog.showMessageBox(remote.getCurrentWindow(), alertError) }
render () { const { config, storageKey } = this.props const { currentSnippet } = this.state 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> <SnippetList onSnippetClick={this.handleSnippetClick.bind(this)} onSnippetDeleted={this.handleDeleteSnippet.bind(this)} /> <div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}> <div styleName='group-section'> <div styleName='group-section-label'>{i18n.__('Snippet name')}</div> <div styleName='group-section-control'> <input styleName='group-section-control-input' value={currentSnippet ? currentSnippet.name : ''} onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'name') }} type='text' /> </div> </div> <div styleName='group-section'> <div styleName='group-section-label'>{i18n.__('Snippet prefix')}</div> <div styleName='group-section-control'> <input styleName='group-section-control-input' value={currentSnippet ? currentSnippet.prefix : ''} onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'prefix') }} type='text' /> </div> </div> <div styleName='snippet-editor-section'> <SnippetEditor storageKey={storageKey} 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} onRef={ref => { this.snippetEditor = ref }} /> </div> </div> </div> ) }
updateApp () { const index = dialog.showMessageBox(remote.getCurrentWindow(), { type: 'warning', message: i18n.__('Update Boostnote'), detail: i18n.__('New Boostnote is ready to be installed.'), buttons: [i18n.__('Restart & Install'), i18n.__('Not Now')] }) if (index === 0) { ipcRenderer.send('update-app-confirm') } }
confirmPublish (note) { const buttonIndex = dialog.showMessageBox(remote.getCurrentWindow(), { type: 'warning', message: i18n.__('Publish Succeeded'), detail: `${note.title} is published at ${note.blog.blogLink}`, buttons: [i18n.__('Confirm'), i18n.__('Open Blog')] }) if (buttonIndex === 1) { this.openBlog(note) } }
render () { const { config, style, location } = this.props return ( <div className='TopBar' styleName={config.isSideNavFolded ? 'root--expanded' : 'root'} style={style} > <div styleName='control'> <div styleName='control-search'> <div styleName='control-search-input' onFocus={(e) => this.handleSearchFocus(e)} onBlur={(e) => this.handleSearchBlur(e)} tabIndex='-1' ref='search' > <input ref='searchInput' value={this.state.search} onChange={(e) => this.handleSearchChange(e)} onKeyDown={(e) => this.handleKeyDown(e)} onKeyUp={(e) => this.handleKeyUp(e)} placeholder={i18n.__('Search')} type='text' className='searchInput' /> {this.state.search !== '' && <button styleName='control-search-input-clear' onClick={(e) => this.handleSearchClearButton(e)} > <i className='fa fa-fw fa-times' /> <span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span> </button> } </div> </div> </div> {location.pathname === '/trashed' ? '' : <NewNoteButton {..._.pick(this.props, [ 'dispatch', 'data', 'config', 'params', 'location' ])} />} </div> ) }
const InfoPanelTrashed = ({ storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml }) => ( <div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}> <div> <p styleName='modification-date'>{updatedAt}</p> <p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p> </div> <hr /> <div> <p styleName='infoPanel-default'>{storageName}</p> <p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p> </div> <div> <p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p> <p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p> </div> <div> <p styleName='infoPanel-default'>{createdAt}</p> <p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p> </div> <div id='export-wrap'> <button styleName='export--enable' onClick={(e) => exportAsMd(e)}> <i className='fa fa-file-code-o' /> <p>.md</p> </button> <button styleName='export--enable' onClick={(e) => exportAsTxt(e)}> <i className='fa fa-file-text-o' /> <p>.txt</p> </button> <button styleName='export--enable' onClick={(e) => exportAsHtml(e)}> <i className='fa fa-html5' /> <p>.html</p> </button> <button styleName='export--unable'> <i className='fa fa-file-pdf-o' /> <p>.pdf</p> </button> </div> </div> )
handleTagContextMenu (e, tag) { const menu = [] menu.push({ label: i18n.__('Delete Tag'), click: this.deleteTag.bind(this, tag) }) menu.push({ label: i18n.__('Customize Color'), click: this.displayColorPicker.bind(this, tag, e.target.getBoundingClientRect()) }) context.popup(menu) }
showWarning (e, msg) { const warningMessage = (msg) => ({ 'export-txt': 'Text export', 'export-md': 'Markdown export', 'export-html': 'HTML export', 'export-pdf': 'PDF export', 'print': 'Print' })[msg] dialog.showMessageBox(remote.getCurrentWindow(), { type: 'warning', message: i18n.__('Sorry!'), detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'), buttons: [i18n.__('OK')] }) }
handleFilterButtonContextMenu (event) { const { data } = this.props const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey)) context.popup([ { label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) } ]) }
render () { const { className } = this.props return ( <button className={_.isString(className) ? 'StarButton ' + className : 'StarButton' } styleName={this.state.isActive || this.props.isActive ? 'root--active' : 'root' } onMouseDown={(e) => this.handleMouseDown(e)} onMouseUp={(e) => this.handleMouseUp(e)} onMouseLeave={(e) => this.handleMouseLeave(e)} onClick={this.props.onClick}> <img styleName='icon' src={this.state.isActive || this.props.isActive ? '../resources/icon/icon-starred.svg' : '../resources/icon/icon-star.svg' } /> <span styleName='tooltip'>{i18n.__('Star')}</span> </button> ) }
showMessageBox (message) { dialog.showMessageBox(remote.getCurrentWindow(), { type: 'warning', message: message, buttons: [i18n.__('OK')] }) }
handleTabDeleteButtonClick (e, index) { if (this.state.note.snippets.length > 1) { if (this.state.note.snippets[index].content.trim().length > 0) { const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), { type: 'warning', message: i18n.__('Delete a snippet'), detail: i18n.__('This work cannot be undone.'), buttons: [i18n.__('Confirm'), i18n.__('Cancel')] }) if (dialogIndex === 0) { this.deleteSnippetByIndex(index) } } else { this.deleteSnippetByIndex(index) } } }
deleteTag (tag) { const selectedButton = remote.dialog.showMessageBox(remote.getCurrentWindow(), { ype: 'warning', message: i18n.__('Confirm tag deletion'), detail: i18n.__('This will permanently remove this tag.'), buttons: [i18n.__('Confirm'), i18n.__('Cancel')] }) if (selectedButton === 0) { const { data, dispatch, location, params } = this.props const notes = data.noteMap .map(note => note) .filter(note => note.tags.indexOf(tag) !== -1) .map(note => { note = Object.assign({}, note) note.tags = note.tags.slice() note.tags.splice(note.tags.indexOf(tag), 1) return note }) Promise .all(notes.map(note => dataApi.updateNote(note.storage, note.key, note))) .then(updatedNotes => { updatedNotes.forEach(note => { dispatch({ type: 'UPDATE_NOTE', note }) }) if (location.pathname.match('/tags')) { const tags = params.tagname.split(' ') const index = tags.indexOf(tag) if (index !== -1) { tags.splice(index, 1) this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`) } } }) } }
const RestoreButton = ({ onClick }) => ( <button styleName='control-restoreButton' onClick={onClick} > <i className='fa fa-undo fa-fw' styleName='iconRestore' /> <span styleName='tooltip'>{i18n.__('Restore')}</span> </button> )
return new Promise((resolve, reject) => { dialog.showOpenDialog({ title: i18n.__('Select Directory'), defaultPath, properties: ['openDirectory', 'createDirectory'] }, function (targetPaths) { if (targetPaths == null) return resolve('') resolve(targetPaths[0]) }) })
const ListButton = ({ onClick, isTagActive }) => ( <button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={onClick}> <img src={isTagActive ? '../resources/icon/icon-list.svg' : '../resources/icon/icon-list-active.svg' } /> <span styleName='tooltip'>{i18n.__('Notes')}</span> </button> )
const TagButton = ({ onClick, isTagActive }) => ( <button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={onClick}> <img src={isTagActive ? '../resources/icon/icon-tag-active.svg' : '../resources/icon/icon-tag.svg' } /> <span styleName='tooltip'>{i18n.__('Tags')}</span> </button> )
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => { const {ui, editor, preview} = this.props.config this.currentConfig = {ui, editor, preview} if (_.isEqual(this.currentConfig, this.state.config)) { this.props.haveToSave() } else { this.props.haveToSave({ tab: 'UI', type: 'warning', message: i18n.__('Unsaved Changes!') }) } })
const ToggleModeButton = ({ onClick, editorType }) => ( <div styleName='control-toggleModeButton'> <div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}> <img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} /> </div> <div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}> <img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} /> </div> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span> </div> )
render () { return ( <div styleName='root'> <div styleName='header'>{i18n.__('Crowdfunding')}</div> <p>{i18n.__('Dear everyone,')}</p> <br /> <p>{i18n.__('Thank you for using Boostnote!')}</p> <p>{i18n.__('Boostnote is used in about 200 different countries and regions by an awesome community of developers.')}</p> <br /> <p>{i18n.__('To continue supporting this growth, and to satisfy community expectations,')}</p> <p>{i18n.__('we would like to invest more time and resources in this project.')}</p> <br /> <p>{i18n.__('If you like this project and see its potential, you can help by supporting us on OpenCollective!')}</p> <br /> <p>{i18n.__('Thanks,')}</p> <p>{i18n.__('Boostnote maintainers')}</p> <br /> <button styleName='cf-link'> <a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('Support via OpenCollective')}</a> </button> </div> ) }
cloneNote () { const { selectedNoteKeys } = this.state const { dispatch, location } = this.props const { storage, folder } = this.resolveTargetFolder() const notes = this.notes.map((note) => Object.assign({}, note)) const selectedNotes = findNotesByKeys(notes, selectedNoteKeys) const firstNote = selectedNotes[0] const eventName = firstNote.type === 'MARKDOWN_NOTE' ? 'ADD_MARKDOWN' : 'ADD_SNIPPET' AwsMobileAnalyticsConfig.recordDynamicCustomEvent(eventName) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') dataApi .createNote(storage.key, { type: firstNote.type, folder: folder.key, title: firstNote.title + ' ' + i18n.__('copy'), content: firstNote.content }) .then((note) => { attachmentManagement.cloneAttachments(firstNote, note) return note }) .then((note) => { dispatch({ type: 'UPDATE_NOTE', note: note }) this.setState({ selectedNoteKeys: [note.key] }) hashHistory.push({ pathname: location.pathname, query: {key: note.key} }) }) }
render () { const { config, status } = this.context return ( <div className='StatusBar' styleName='root' > <button styleName='zoom' onClick={(e) => this.handleZoomButtonClick(e)} > <img src='../resources/icon/icon-zoom.svg' /> <span>{Math.floor(config.zoom * 100)}%</span> </button> {status.updateReady ? <button onClick={this.updateApp} styleName='update'> <i styleName='update-icon' className='fa fa-cloud-download' /> {i18n.__('Ready to Update!')} </button> : null } </div> ) }
render () { return ( <div styleName='root' tabIndex='-1' onKeyDown={(e) => this.handleKeyDown(e)} > <div styleName='header'> <div styleName='title'>{i18n.__('Make a note')}</div> </div> <ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} /> <div styleName='control'> <button styleName='control-button' onClick={(e) => this.handleMarkdownNoteButtonClick(e)} onKeyDown={(e) => this.handleMarkdownNoteButtonKeyDown(e)} ref='markdownButton' > <i styleName='control-button-icon' className='fa fa-file-text-o' /><br /> <span styleName='control-button-label'>{i18n.__('Markdown Note')}</span><br /> <span styleName='control-button-description'>{i18n.__('This format is for creating text documents. Checklists, code blocks and Latex blocks are available.')}</span> </button> <button styleName='control-button' onClick={(e) => this.handleSnippetNoteButtonClick(e)} onKeyDown={(e) => this.handleSnippetNoteButtonKeyDown(e)} ref='snippetButton' > <i styleName='control-button-icon' className='fa fa-code' /><br /> <span styleName='control-button-label'>{i18n.__('Snippet Note')}</span><br /> <span styleName='control-button-description'>{i18n.__('This format is for creating code snippets. Multiple snippets can be grouped into a single note.')} </span> </button> </div> <div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div> </div> ) }
SideNavComponent (isFolded, storageList) { const { location, data, config } = this.props const isHomeActive = !!location.pathname.match(/^\/home$/) const isStarredActive = !!location.pathname.match(/^\/starred$/) const isTrashedActive = !!location.pathname.match(/^\/trashed$/) let component // TagsMode is not selected if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) { component = ( <div> <SideNavFilter isFolded={isFolded} isHomeActive={isHomeActive} handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)} isStarredActive={isStarredActive} isTrashedActive={isTrashedActive} handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)} handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)} counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size} counterStarredNote={data.starredSet._set.size} counterDelNote={data.trashedSet._set.size} handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)} /> <StorageList storageList={storageList} isFolded={isFolded} /> <NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} /> </div> ) } else { component = ( <div styleName='tabBody'> <div styleName='tag-control'> <div styleName='tag-control-title'> <p>{i18n.__('Tags')}</p> </div> <div styleName='tag-control-sortTagsBy'> <i className='fa fa-angle-down' /> <select styleName='tag-control-sortTagsBy-select' title={i18n.__('Select filter mode')} value={config.sortTagsBy} onChange={(e) => this.handleSortTagsByChange(e)} > <option title='Sort alphabetically' value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option> <option title='Sort by update time' value='COUNTER'>{i18n.__('Counter')}</option> </select> </div> </div> <div styleName='tagList'> {this.tagListComponent(data)} </div> </div> ) } return component }
render () { const { location, config } = this.props let { notes } = this.props const { selectedNoteKeys } = this.state const sortFunc = config.sortBy === 'CREATED_AT' ? sortByCreatedAt : config.sortBy === 'ALPHABETICAL' ? sortByAlphabetical : sortByUpdatedAt const sortedNotes = location.pathname.match(/\/starred|\/trash/) ? this.getNotes().sort(sortFunc) : this.sortByPin(this.getNotes().sort(sortFunc)) this.notes = notes = sortedNotes.filter((note) => { // this is for the trash box if (note.isTrashed !== true || location.pathname === '/trashed') return true }) moment.updateLocale('en', { relativeTime: { future: 'in %s', past: '%s ago', s: '%ds', ss: '%ss', m: '1m', mm: '%dm', h: 'an hour', hh: '%dh', d: '1d', dd: '%dd', M: '1M', MM: '%dM', y: '1Y', yy: '%dY' } }) const viewType = this.getViewType() const autoSelectFirst = notes.length === 1 || selectedNoteKeys.length === 0 || notes.every(note => !selectedNoteKeys.includes(note.key)) const noteList = notes .map((note, index) => { if (note == null) { return null } const isDefault = config.listStyle === 'DEFAULT' const uniqueKey = getNoteKey(note) const isActive = selectedNoteKeys.includes(uniqueKey) || notes.length === 1 || (autoSelectFirst && index === 0) const dateDisplay = moment( config.sortBy === 'CREATED_AT' ? note.createdAt : note.updatedAt ).fromNow('D') if (isDefault) { return ( <NoteItem isActive={isActive} note={note} dateDisplay={dateDisplay} key={uniqueKey} handleNoteContextMenu={this.handleNoteContextMenu.bind(this)} handleNoteClick={this.handleNoteClick.bind(this)} handleDragStart={this.handleDragStart.bind(this)} pathname={location.pathname} folderName={this.getNoteFolder(note).name} storageName={this.getNoteStorage(note).name} viewType={viewType} /> ) } return ( <NoteItemSimple isActive={isActive} note={note} key={uniqueKey} handleNoteContextMenu={this.handleNoteContextMenu.bind(this)} handleNoteClick={this.handleNoteClick.bind(this)} handleDragStart={this.handleDragStart.bind(this)} pathname={location.pathname} folderName={this.getNoteFolder(note).name} storageName={this.getNoteStorage(note).name} viewType={viewType} /> ) }) return ( <div className='NoteList' styleName='root' style={this.props.style} onDrop={(e) => this.handleDrop(e)} > <div styleName='control'> <div styleName='control-sortBy'> <i className='fa fa-angle-down' /> <select styleName='control-sortBy-select' title={i18n.__('Select filter mode')} value={config.sortBy} onChange={(e) => this.handleSortByChange(e)} > <option title='Sort by update time' value='UPDATED_AT'>{i18n.__('Updated')}</option> <option title='Sort by create time' value='CREATED_AT'>{i18n.__('Created')}</option> <option title='Sort alphabetically' value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option> </select> </div> <div styleName='control-button-area'> <button title={i18n.__('Default View')} styleName={config.listStyle === 'DEFAULT' ? 'control-button--active' : 'control-button' } onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')} > <img styleName='iconTag' src='../resources/icon/icon-column.svg' /> </button> <button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL' ? 'control-button--active' : 'control-button' } onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')} > <img styleName='iconTag' src='../resources/icon/icon-column-list.svg' /> </button> </div> </div> <div styleName='list' ref='list' tabIndex='-1' onKeyDown={(e) => this.handleNoteListKeyDown(e)} onKeyUp={this.handleNoteListKeyUp} > {noteList} </div> </div> ) }
handleNoteContextMenu (e, uniqueKey) { const { location } = this.props const { selectedNoteKeys } = this.state const note = findNoteByKey(this.notes, uniqueKey) const noteKey = getNoteKey(note) if (selectedNoteKeys.length === 0 || !selectedNoteKeys.includes(noteKey)) { this.handleNoteClick(e, uniqueKey) } const pinLabel = note.isPinned ? i18n.__('Remove pin') : i18n.__('Pin to Top') const deleteLabel = i18n.__('Delete Note') const cloneNote = i18n.__('Clone Note') const restoreNote = i18n.__('Restore Note') const copyNoteLink = i18n.__('Copy Note Link') const publishLabel = i18n.__('Publish Blog') const updateLabel = i18n.__('Update Blog') const openBlogLabel = i18n.__('Open Blog') const templates = [] if (location.pathname.match(/\/trash/)) { templates.push({ label: restoreNote, click: this.restoreNote }, { label: deleteLabel, click: this.deleteNote }) } else { if (!location.pathname.match(/\/starred/)) { templates.push({ label: pinLabel, click: this.pinToTop }) } templates.push({ label: deleteLabel, click: this.deleteNote }, { label: cloneNote, click: this.cloneNote.bind(this) }, { label: copyNoteLink, click: this.copyNoteLink(note) }) if (note.type === 'MARKDOWN_NOTE') { if (note.blog && note.blog.blogLink && note.blog.blogId) { templates.push({ label: updateLabel, click: this.publishMarkdown.bind(this) }, { label: openBlogLabel, click: () => this.openBlog.bind(this)(note) }) } else { templates.push({ label: publishLabel, click: this.publishMarkdown.bind(this) }) } } } context.popup(templates) }
render () { const { data, config, location } = this.props const { note } = this.state const storageKey = note.storage const folderKey = note.folder const autoDetect = config.editor.snippetDefaultLanguage === 'Auto Detect' 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 const tabList = note.snippets.map((snippet, index) => { const isActive = this.state.snippetIndex === index return <SnippetTab key={index} ref={'tab-' + index} snippet={snippet} isActive={isActive} onClick={(e) => this.handleTabButtonClick(e, index)} onDelete={(e) => this.handleTabDeleteButtonClick(e, index)} onRename={(name) => this.renameSnippetByIndex(index, name)} isDeletable={note.snippets.length > 1} onDragStart={(e) => this.handleTabDragStart(e, index)} onDrop={(e) => this.handleTabDrop(e, index)} /> }) const viewList = note.snippets.map((snippet, index) => { const isActive = this.state.snippetIndex === index return <div styleName='tabView' key={index} style={{zIndex: isActive ? 5 : 4}} > {snippet.mode === 'Markdown' || snippet.mode === 'GitHub Flavored Markdown' ? <MarkdownEditor styleName='tabView-content' value={snippet.content} config={config} linesHighlighted={snippet.linesHighlighted} onChange={(e) => this.handleCodeChange(index)(e)} ref={'code-' + index} ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents} storageKey={storageKey} /> : <CodeEditor styleName='tabView-content' mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)} value={snippet.content} linesHighlighted={snippet.linesHighlighted} theme={config.editor.theme} fontFamily={config.editor.fontFamily} fontSize={editorFontSize} indentType={config.editor.indentType} indentSize={editorIndentSize} displayLineNumbers={config.editor.displayLineNumbers} matchingPairs={config.editor.matchingPairs} matchingTriples={config.editor.matchingTriples} explodingPairs={config.editor.explodingPairs} keyMap={config.editor.keyMap} scrollPastEnd={config.editor.scrollPastEnd} fetchUrlTitle={config.editor.fetchUrlTitle} enableTableEditor={config.editor.enableTableEditor} onChange={(e) => this.handleCodeChange(index)(e)} ref={'code-' + index} enableSmartPaste={config.editor.enableSmartPaste} hotkey={config.hotkey} autoDetect={autoDetect} /> } </div> }) const options = [] data.storageMap.forEach((storage, index) => { storage.folders.forEach((folder) => { options.push({ storage: storage, folder: folder }) }) }) const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0] const trashTopBar = <div styleName='info'> <div styleName='info-left'> <RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} /> </div> <div styleName='info-right'> <PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} /> <InfoButton onClick={(e) => this.handleInfoButtonClick(e)} /> <InfoPanelTrashed storageName={currentOption.storage.name} folderName={currentOption.folder.name} updatedAt={formatDate(note.updatedAt)} createdAt={formatDate(note.createdAt)} exportAsMd={this.showWarning} exportAsTxt={this.showWarning} exportAsHtml={this.showWarning} exportAsPdf={this.showWarning} /> </div> </div> const detailTopBar = <div styleName='info'> <div styleName='info-left'> <div styleName='info-left-top'> <FolderSelect styleName='info-left-top-folderSelect' value={this.state.note.storage + '-' + this.state.note.folder} ref='folder' data={data} onChange={(e) => this.handleFolderChange(e)} /> </div> <TagSelect ref='tags' value={this.state.note.tags} saveTagsAlphabetically={config.ui.saveTagsAlphabetically} showTagsAlphabetically={config.ui.showTagsAlphabetically} data={data} onChange={(e) => this.handleChange(e)} coloredTags={config.coloredTags} /> </div> <div styleName='info-right'> <StarButton onClick={(e) => this.handleStarButtonClick(e)} isActive={note.isStarred} /> <FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} /> <TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <InfoButton onClick={(e) => this.handleInfoButtonClick(e)} /> <InfoPanel storageName={currentOption.storage.name} folderName={currentOption.folder.name} noteLink={`[${note.title}](:note:${location.query.key})`} updatedAt={formatDate(note.updatedAt)} createdAt={formatDate(note.createdAt)} exportAsMd={this.showWarning} exportAsTxt={this.showWarning} exportAsHtml={this.showWarning} exportAsPdf={this.showWarning} type={note.type} print={this.showWarning} /> </div> </div> return ( <div className='NoteDetail' style={this.props.style} styleName='root' onKeyDown={(e) => this.handleKeyDown(e)} > {location.pathname === '/trashed' ? trashTopBar : detailTopBar} <div styleName='body'> <div styleName='description'> <textarea style={{ fontFamily: config.preview.fontFamily, fontSize: parseInt(config.preview.fontSize, 10) }} ref='description' placeholder={i18n.__('Description...')} value={this.state.note.description} onChange={(e) => this.handleChange(e)} /> </div> <div styleName='tabList'> <button styleName='tabButton' hidden={!this.state.showArrows} disabled={!this.state.enableLeftArrow} onClick={(e) => this.handleTabMoveLeftButtonClick(e)} > <i className='fa fa-chevron-left' /> </button> <div styleName='list' onScroll={(e) => { this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}> <div styleName='allTabs' ref={(tabs) => { this.allTabs = tabs }}> {tabList} </div> </div> <button styleName='tabButton' hidden={!this.state.showArrows} disabled={!this.state.enableRightArrow} onClick={(e) => this.handleTabMoveRightButtonClick(e)} > <i className='fa fa-chevron-right' /> </button> <button styleName='tabButton' onClick={(e) => this.handleTabPlusButtonClick(e)} > <i className='fa fa-plus' /> </button> </div> {viewList} </div> <div styleName='override'> <button onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)} > {this.state.note.snippets[this.state.snippetIndex].mode == null ? i18n.__('Select Syntax...') : this.state.note.snippets[this.state.snippetIndex].mode } <i className='fa fa-caret-down' /> </button> <button onClick={(e) => this.handleIndentTypeButtonClick(e)} > Indent: {config.editor.indentType} <i className='fa fa-caret-down' /> </button> <button onClick={(e) => this.handleIndentSizeButtonClick(e)} > size: {config.editor.indentSize} <i className='fa fa-caret-down' /> </button> </div> <StatusBar {..._.pick(this.props, ['config', 'location', 'dispatch'])} date={note.updatedAt} /> </div> ) }