Example #1
0
  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>
    )
  }
Example #2
0
 showWarning () {
   dialog.showMessageBox(remote.getCurrentWindow(), {
     type: 'warning',
     message: i18n.__('Sorry!'),
     detail: i18n.__('md/text import is available only a markdown note.'),
     buttons: [i18n.__('OK')]
   })
 }
Example #3
0
 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')]
     })
   }
 }
Example #4
0
 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)
 }
Example #5
0
  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>
    )
  }
Example #6
0
  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')
    }
  }
Example #7
0
  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)
    }
  }
Example #8
0
 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>
)
Example #10
0
  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)
  }
Example #11
0
  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')]
    })
  }
Example #12
0
 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) }
   ])
 }
Example #13
0
  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>
    )
  }
Example #14
0
 showMessageBox (message) {
   dialog.showMessageBox(remote.getCurrentWindow(), {
     type: 'warning',
     message: message,
     buttons: [i18n.__('OK')]
   })
 }
Example #15
0
 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)
     }
   }
 }
Example #16
0
  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(' ')}`)
            }
          }
        })
    }
  }
Example #17
0
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>
)
Example #18
0
 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])
   })
 })
Example #19
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>
)
Example #20
0
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>
)
Example #21
0
 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!')
     })
   }
 })
Example #22
0
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>
)
Example #23
0
 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>
   )
 }
Example #24
0
  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}
        })
      })
  }
Example #25
0
  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>
    )
  }
Example #26
0
  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>
    )
  }
Example #27
0
  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
  }
Example #28
0
  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>
    )
  }
Example #29
0
  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)
  }
Example #30
0
  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
            }&nbsp;
            <i className='fa fa-caret-down' />
          </button>
          <button
            onClick={(e) => this.handleIndentTypeButtonClick(e)}
          >
            Indent: {config.editor.indentType}&nbsp;
            <i className='fa fa-caret-down' />
          </button>
          <button
            onClick={(e) => this.handleIndentSizeButtonClick(e)}
          >
            size: {config.editor.indentSize}&nbsp;
            <i className='fa fa-caret-down' />
          </button>
        </div>

        <StatusBar
          {..._.pick(this.props, ['config', 'location', 'dispatch'])}
          date={note.updatedAt}
        />
      </div>
    )
  }