52 Commits
Author SHA1 Message Date
Seongjae Lee 7e12c57f0b Rename startWatcher to initWatcher to match initSearchIndex 2016-07-05 01:07:03 -07:00
Seongjae Lee 81c36a7521 Upgrade lunr to 0.7.1 2016-07-05 01:05:56 -07:00
Seongjae Lee a73b308639 Fix a bug that throwing an error when enableLunrPipeline is off
Also, enableLunrPipeline option should match on restoring lunr.
2016-07-05 01:05:42 -07:00
Seongjae Lee 4eb6ada48a Fix 54, uncaught error: cannot find module 'docquery' 2016-07-03 22:15:07 -07:00
Seongjae Lee 31a2b6a66e Remove unnecessary code from note-cache.coffee 2016-07-03 11:06:53 -07:00
Seongjae Lee 68c8b47ffb Fix a note-watcher error during deactivation 2016-07-03 10:44:32 -07:00
Seongjae Lee b22d94450c Use note watcher's save/load functionality 2016-07-03 01:31:40 -07:00
Seongjae Lee f0b1fcd075 Replace docquery to note-watcher, which supports lunr search index loading
NoteWatcher holds NoteCache, which caches recent notes and file modified time to help search index recovery.
2016-07-02 23:20:41 -07:00
Seongjae Lee 1f005a3025 Prepare 0.11.0 release 2016-06-23 22:43:51 -07:00
Seongjae Lee 2cbf6a63d7 Update changelog 2016-06-23 22:43:25 -07:00
Seongjae Lee 3c36f51fe6 Add an option to enable/disable autosave, resolving #50
It would be more efficient if we can subscribe/unsubscribe autosave whenver this parameter is changed, but I don't think the current implementation would be that expansive.
2016-06-23 22:38:00 -07:00
Seongjae Lee 956c0017e4 Prepare 0.10.1 release 2016-06-23 19:09:31 -07:00
Seongjae Lee 0dd99d805b Fix #52 to prevent spitting an error message when whitespace package is not active 2016-06-23 19:07:40 -07:00
Seongjae Lee aa641ba695 Prepare 0.10.0 release 2016-04-25 18:47:08 -07:00
Seongjae Lee 6a7e04d177 Merge pull request #48 from jonmagic/patch-1
Update docquery to 1.1.1
2016-04-25 18:43:03 -07:00
Jonathan Hoyt 5aab86660f Update docquery to 1.1.1 2016-04-24 21:49:26 -07:00
Seongjae Lee 6ec1c4c2e8 Prepare 0.9.3 release 2015-10-17 00:14:44 -07:00
Seongjae Lee f582f741ad Merge pull request #38 from xHN35RQ/master
Add Windows/Linux keymap note
2015-10-17 00:14:13 -07:00
Seongjae Lee 6fe704aad1 Prepare 0.9.2 release 2015-10-16 23:49:32 -07:00
Seongjae Lee 669839750c Fix the double padding problem in the search window 2015-10-16 23:49:20 -07:00
Nic 452c34ba36 Add Windows/Linux keymap note 2015-10-16 23:03:11 -07:00
Seongjae Lee ee5dc2a87a Prepare 0.9.1 release 2015-10-16 22:33:32 -07:00
Seongjae Lee bd7d47474c Add interlink menu item 2015-10-16 22:33:11 -07:00
Seongjae Lee 424f2a13d6 Prepare 0.9.0 release 2015-10-16 22:17:43 -07:00
Seongjae Lee 04b7df8676 Update changelog 2015-10-16 22:16:23 -07:00
Seongjae Lee 2d2a070d1e Fix a bug that utility.isNote does not handle symlinks properly 2015-10-07 07:55:36 -07:00
Seongjae Lee 6ae37437be Load nvatom grammar on atom 2015-09-28 21:42:23 -07:00
Seongjae Lee 51f769a017 Apply the interlink extended grammar only on valid notes
To do so, we need to activate the package on Atom startup.
2015-09-28 01:14:39 -07:00
Seongjae Lee ca7c14e3b7 Rename notelink to interlink 2015-09-27 10:45:51 -07:00
Seongjae Lee c5eca919ed Add tests for notelinks 2015-09-27 10:39:03 -07:00
Seongjae Lee 9244b0a0b9 Update readme for interlinks 2015-09-26 10:34:02 -07:00
Seongjae Lee 323fb29e56 Add tests for utility class 2015-09-26 10:27:46 -07:00
Seongjae Lee 6c55f58774 Fix uncaught reference error for not including utility 2015-09-26 07:52:03 -07:00
Seongjae Lee 9c5c4e43da Add interlink highlight and jump features 2015-09-26 00:16:36 -07:00
Seongjae Lee cc8a780436 Prepare 0.8.2 release 2015-09-24 21:25:23 -07:00
Seongjae Lee 431b929208 Merge pull request #33 from lexicalunit/normalize_directory
Normalize nvatom.directory so that ~/... works correctly.
2015-09-24 21:22:32 -07:00
lexicalunit 61d071c15a Normalize nvatom.directory so that ~/... works correctly. 2015-09-24 18:36:59 -05:00
Seongjae Lee 4e4688fe78 Prepare 0.8.1 release 2015-09-16 19:56:14 -07:00
Seongjae Lee 0fffc6a6b1 Avoid using regexp for searching title
Fixes #28.
2015-09-16 19:56:00 -07:00
Seongjae Lee 547617f485 Refactor code based on CoffeeScript style guide
https://github.com/polarmobile/coffeescript-style-guide
2015-09-12 12:14:56 -07:00
Seongjae Lee 74735eec82 Prepare 0.8.0 release 2015-09-12 10:52:12 -07:00
Seongjae Lee fa873af344 Update changelog 2015-09-12 10:51:48 -07:00
Seongjae Lee a31f490a70 Fix a bug on warning messages
Ruby-style string interpolation is only with double-quoted strings.
2015-09-12 10:49:44 -07:00
Seongjae Lee 27f2a5bb8d Delete an empty note when closing a pane
Close #18. This is against nvALT, but I think this is more intuitive way.
2015-09-12 10:47:29 -07:00
Seongjae Lee 95093df286 Prepare 0.7.1 release 2015-09-12 10:17:10 -07:00
Seongjae Lee 5e4345f719 Make autosave also consider nvatom.extensions 2015-09-12 10:16:52 -07:00
Seongjae Lee cee47a8fd7 Prepare 0.7.0 release 2015-09-12 02:02:59 -07:00
Seongjae Lee 15bf1e741b Update changelog 2015-09-12 02:02:43 -07:00
Seongjae Lee bd19c270b3 Support other extensions than ".md"
Close #13.
2015-09-12 01:58:59 -07:00
Seongjae Lee b5f7ba7b75 Prepare 0.6.0 release 2015-09-12 01:36:40 -07:00
Seongjae Lee da888fa461 Prepare to move git repository from notational-velocity to nvatom 2015-09-12 01:36:12 -07:00
Seongjae Lee 97830b31db Prepare to move git repository from notational-velocity to nvatom 2015-09-12 01:33:38 -07:00
17 changed files with 970 additions and 79 deletions
+14 -1
View File
@@ -1,5 +1,18 @@
## 0.11.0
- Make autosave a selectable feature
- Fix the bug that shows an error message on save when whitespace package is disabled
## 0.9.0
- Add an interlink feature
## 0.8.0
- Add a feature to delete an empty note automatically when closing its pane
## 0.7.0
- Add `extensions` setting
## 0.5.0
- Add `useLunrPipeline` feature
- Add `useLunrPipeline` setting
- Improve autoselect/autocomplete logic
- Fix a bug that autocomplete feature does not work
+14 -3
View File
@@ -13,6 +13,7 @@ This Atom package implements the some key features from this amazing app:
- Modeless operation
- Mouseless interaction
- Incremental Search
- Interlinks
Integrated with Atom, we have several advantages:
@@ -47,6 +48,7 @@ Double-quotes also work.
## Key Bindings
- `alt-cmd-l`: Toggles the search view.
- `alt-cmd-o`: Jumps to the referred note when the cursor is on the interlink in the form of [[double-bracketed interlink]].
You can also override `cmd-l` if you want to keep your muscle memory from Notational Velocity and nvALT. Just edit your keymap (Atom menu -> Open Your Keymap) and add the following lines:
@@ -57,6 +59,15 @@ You can also override `cmd-l` if you want to keep your muscle memory from Notati
'cmd-l': 'nvatom:toggle'
```
To set Windows and Linux key binds you can add the following lines to your keymap:
```cson
'atom-workspace':
'alt-ctrl-l': 'nvatom:toggle'
'atom-workspace atom-text-editor':
'alt-ctrl-o': 'nvatom:openInterlink'
```
## Migration
v0.1.0 under published package name `notational-velocity` had a fatal bug that sets the default value of its note directory under package directory. In that case, all notes are deleted once the user updates the package, since package directory is overwritten when the user. For more information, refer [#25][6].
@@ -76,7 +87,7 @@ Since keymaps overlap with `notational-velocity`, follow the menu *Packages > nv
[1]: http://notational.net/
[2]: http://daringfireball.net/projects/markdown/syntax
[3]: https://travis-ci.org/seongjaelee/notational-velocity.svg?branch=master
[4]: https://travis-ci.org/seongjaelee/notational-velocity
[3]: https://travis-ci.org/seongjaelee/nvatom.svg?branch=master
[4]: https://travis-ci.org/seongjaelee/nvatom
[5]: https://cloud.githubusercontent.com/assets/948301/7246990/2e2b4c6e-e7b9-11e4-93b0-57954e011e81.gif
[6]: https://github.com/seongjaelee/notational-velocity/issues/25
[6]: https://github.com/seongjaelee/nvatom/issues/25
+18
View File
@@ -0,0 +1,18 @@
'name': 'nvAtom Github Markdown'
'scopeName': 'source.gfm.nvatom'
'patterns': [
{
'include': 'source.gfm'
}
{
'match': '(?<!\\[)(\\[\\[)([^\\[\\]\|]+)(\\]\\])(?!\\])'
'name': 'interlink'
'captures':
'1':
'name': 'punctuation.definition.begin.gfm'
'2':
'name': 'markup.underline.link.interlink.gfm'
'3':
'name': 'punctuation.definition.begin.gfm'
}
]
+2
View File
@@ -9,3 +9,5 @@
# https://atom.io/docs/latest/advanced/keymaps
'atom-workspace':
'alt-cmd-l': 'nvatom:toggle'
'atom-workspace atom-text-editor':
'alt-cmd-o': 'nvatom:openInterlink'
+48
View File
@@ -0,0 +1,48 @@
fs = require 'fs-plus'
path = require 'path'
{CompositeDisposable} = require 'atom'
Utility = require './utility'
module.exports =
class Interlink
constructor: ->
Interlink.loadGrammarSync()
@subscriptions = new CompositeDisposable
@subscriptions.add atom.commands.add 'atom-workspace', 'nvatom:openInterlink': => Interlink.openInterlink()
@subscriptions.add atom.workspace.observeTextEditors (editor) ->
if Utility.isNote(editor.getPath())
editor.setGrammar(atom.grammars.grammarForScopeName('source.gfm.nvatom'))
destroy: ->
@subscriptions.dispose()
@loadGrammarSync: ->
unless atom.grammars.grammarForScopeName('source.gfm.nvatom')
grammarPath = path.join(atom.packages.resolvePackagePath('nvatom'), 'grammars', 'nvatom.cson')
atom.grammars.loadGrammarSync(grammarPath)
@openInterlink: ->
editor = atom.workspace.getActiveTextEditor()
return unless editor?
return unless Utility.isNote(editor.getPath())
noteTitle = Interlink.getInterlinkUnderCursor(editor)
return unless noteTitle?
return unless noteTitle.length
notePath = Utility.getNotePath(noteTitle)
unless fs.existsSync(notePath)
fs.writeFileSync(notePath, '')
atom.workspace.open(notePath)
@getInterlinkUnderCursor: (editor) ->
cursorPosition = editor.getCursorBufferPosition()
token = editor.tokenForBufferPosition(cursorPosition)
return unless token
return unless token.value
return unless token.scopes.indexOf('markup.underline.link.interlink.gfm') > -1
interlink = Utility.trim(token.value)
return unless interlink.length
return interlink
+24 -28
View File
@@ -2,34 +2,29 @@ path = require 'path'
fs = require 'fs-plus'
_ = require 'underscore-plus'
{$, $$, SelectListView} = require 'atom-space-pen-views'
DocQuery = require 'docquery'
NoteWatcher = require './note-watcher'
Utility = require './utility'
module.exports =
class NotationalVelocityView extends SelectListView
initialize: (state) ->
@initializedAt = new Date()
super
@addClass('nvatom from-top overlay')
@rootDirectory = atom.config.get('nvatom.directory')
if !fs.existsSync(@rootDirectory)
@addClass('nvatom')
@maxItems = 100
@rootDirectory = Utility.getNoteDirectory()
unless fs.existsSync(@rootDirectory)
throw new Error("The given directory #{@rootDirectory} does not exist. "
+ "Set the note directory to the existing one from Settings.")
@skipPopulateList = false
@prevCursorPosition = 0
@documentsLoaded = false
@docQuery = new DocQuery(@rootDirectory, {recursive: true})
@docQuery.on "ready", () =>
@noteWatcher = new NoteWatcher(@rootDirectory, atom.config.get('nvatom.extensions'), @maxItems, atom.config.get('nvatom.enableLunrPipeline'))
@noteWatcher.on "ready", () =>
@documentsLoaded = true
@setLoading()
@populateList()
@docQuery.on "added", (fileDetails) =>
@noteWatcher.on "update", () =>
@populateList() if @documentsLoaded
@docQuery.on "updated", (fileDetails) =>
@populateList() if @documentsLoaded
@docQuery.on "removed", (fileDetails) =>
@populateList() if @documentsLoaded
if !atom.config.get('nvatom.enableLunrPipeline')
@docQuery.searchIndex.pipeline.reset()
isCursorProceeded: ->
editor = @filterEditorView.model
@@ -42,14 +37,14 @@ class NotationalVelocityView extends SelectListView
isCursorProceeded = @isCursorProceeded()
for item in filteredItems
if item.title.match(///^#{filterQuery}$///i) != null
if item.title.toLowerCase() is filterQuery.toLowerCase()
# autoselect
n = filteredItems.indexOf(item) + 1
@selectItemView(@list.find("li:nth-child(#{n})"))
return
for item in filteredItems
if item.title.match(///^#{filterQuery}///i) != null && isCursorProceeded
if item.title.toLowerCase().startsWith(filterQuery.toLowerCase()) and isCursorProceeded
# autocomplete
@skipPopulateList = true
editor = @filterEditorView.model
@@ -61,9 +56,7 @@ class NotationalVelocityView extends SelectListView
@selectItemView(@list.find("li:nth-child(#{n})"))
filter: (filterQuery) ->
if filterQuery == "" || filterQuery == undefined
return @docQuery.documents
return @docQuery.search(filterQuery)
@noteWatcher.search(filterQuery)
getFilterKey: ->
'filetext'
@@ -79,20 +72,18 @@ class NotationalVelocityView extends SelectListView
@show()
viewForItem: (item) ->
content = item.body[0...100]
$$ ->
@li class: 'two-lines', =>
@div class: 'primary-line', =>
@span "#{item.title}"
@div class: 'metadata', "#{item.modifiedAt.toLocaleDateString()}"
@div class: 'secondary-line', "#{content}"
@div class: 'secondary-line', "#{item.body}"
confirmSelection: ->
item = @getSelectedItem()
sanitizedQuery = Utility.trim(@getFilterQuery())
calculatedPath = Utility.getNotePath(sanitizedQuery)
filePath = null
sanitizedQuery = @getFilterQuery().replace(/\s+$/, '')
calculatedPath = path.join(@rootDirectory, sanitizedQuery + '.md')
if item?
filePath = item.filePath
else if fs.existsSync(calculatedPath)
@@ -104,9 +95,12 @@ class NotationalVelocityView extends SelectListView
if filePath
atom.workspace.open(filePath).then (editor) ->
save = ->
atom.packages.deactivatePackage 'whitespace'
isWhiteSpaceActive = atom.packages.isPackageActive 'whitespace'
if isWhiteSpaceActive
atom.packages.deactivatePackage 'whitespace'
editor.save()
atom.packages.activatePackage 'whitespace'
if isWhiteSpaceActive
atom.packages.activatePackage 'whitespace'
debouncedSave = _.debounce save, 1000
editor.onDidStopChanging () ->
debouncedSave() if editor.isModified()
@@ -116,6 +110,8 @@ class NotationalVelocityView extends SelectListView
destroy: ->
@cancel()
@panel?.destroy()
@noteWatcher.save()
@noteWatcher.close()
show: ->
@storeFocusedElement()
@@ -152,9 +148,9 @@ class NotationalVelocityView extends SelectListView
@selectItem(filteredItems, filterQuery)
else
@setError(@getEmptyMessage(@docQuery.documents.length, filteredItems.length))
@setError(@getEmptyMessage(@noteWatcher.length, filteredItems.length))
schedulePopulateList: ->
if !@skipPopulateList
unless @skipPopulateList
super
@skipPopulateList = false
+40 -17
View File
@@ -1,6 +1,8 @@
path = require 'path'
fs = require 'fs-plus'
{CompositeDisposable, Disposable} = require 'atom'
Interlink = require './interlink'
Utility = require './utility'
module.exports =
config:
@@ -9,22 +11,29 @@ module.exports =
description: 'The directory to archive notes'
type: 'string'
default: path.join(process.env.ATOM_HOME, 'nvatom-notes')
extensions:
title: 'Extensions'
description: 'The first extension will be used for newly created notes.'
type: 'array'
default: ['.md', '.txt']
items:
type: 'string'
enableLunrPipeline:
title: 'Enable Lunr Pipeline'
description: 'Lunr pipeline preprocesses query to make search faster. However, it will skip searching some of stop words such as "an" or "be".'
type: 'boolean'
default: true
notationalVelocityView: null
enableAutosave:
title: 'Enable Autosave'
description: 'Enable saving the document automatically whenever the user leaves the window or change the tab.'
type: 'boolean'
default: true
activate: (state) ->
@rootDirectory = @ensureNoteDirectory()
# Events subscribed to in atom's system can be easily cleaned up with a
# CompositeDisposable
@subscriptions = new CompositeDisposable
# Register command that toggles this view
@subscriptions.add atom.commands.add 'atom-workspace',
'nvatom:toggle': => @createView(state).toggle()
@@ -40,14 +49,17 @@ module.exports =
window.addEventListener('blur', handleBlur, true)
@subscriptions.add new Disposable -> window.removeEventListener('blur', handleBlur, true)
@subscriptions.add atom.workspace.onWillDestroyPaneItem ({item}) => @autosave(item)
@subscriptions.add atom.workspace.onWillDestroyPaneItem ({item}) => @autosave(item) unless @autodelete(item)
@interlink = new Interlink()
deactivate: ->
@subscriptions.dispose()
@notationalVelocityView.destroy()
@interlnk?.destroy()
@notationalVelocityView?.destroy()
serialize: ->
notationalVelocityViewState: @notationalVelocityView.serialize()
notationalVelocityViewState: @notationalVelocityView?.serialize()
createView: (state, docQuery) ->
unless @notationalVelocityView?
@@ -56,29 +68,40 @@ module.exports =
@notationalVelocityView
autosave: (paneItem) ->
return unless atom.config.get('nvatom.enableAutosave')
return unless paneItem?.getURI?()?
return unless paneItem?.isModified?()
uri = paneItem.getURI()
return unless uri.indexOf(@rootDirectory) == 0
return unless fs.isMarkdownExtension(path.extname(uri))
return unless Utility.isNote(uri)
paneItem?.save?()
autodelete: (paneItem) ->
return false unless paneItem?.getURI?()?
uri = paneItem.getURI()
return false unless Utility.isNote(uri)
return false unless paneItem.isEmpty()
fs.unlinkSync(uri)
noteName = uri.substring(@rootDirectory.length + 1)
atom.notifications.addInfo("Empty note #{noteName} is deleted.")
return true
autosaveAll: ->
return unless atom.config.get('nvatom.enableAutosave')
@autosave(paneItem) for paneItem in atom.workspace.getPaneItems()
ensureNoteDirectory: ->
noteDirectory = atom.config.get('nvatom.directory')
noteDirectory = fs.normalize(atom.config.get('nvatom.directory'))
packagesDirectory = path.join(process.env.ATOM_HOME, 'packages')
defaultNoteDirectory = path.join(packagesDirectory, 'nvatom', 'notebook')
if noteDirectory.startsWith(packagesDirectory)
throw new Error('Note directory #{noteDirectory} cannot reside within atom packages directory. Please change its value from package settings.')
throw new Error("Note directory #{noteDirectory} cannot reside within atom packages directory. Please change its value from package settings.")
# Initialize note directory.
if !fs.existsSync(noteDirectory)
unless fs.existsSync(noteDirectory)
@tryMigrateFromNotationalVelocity()
noteDirectory = atom.config.get('nvatom.directory')
if !fs.existsSync(noteDirectory)
unless fs.existsSync(noteDirectory)
fs.makeTreeSync(noteDirectory)
fs.copySync(defaultNoteDirectory, noteDirectory)
@@ -91,13 +114,13 @@ module.exports =
defaultNoteDirectory = path.join(packagesDirectory, 'nvatom', 'notebook')
# notational-velocity does not exist.
if prevNoteDirectory == undefined
if prevNoteDirectory is undefined
return
atom.notifications.addInfo('Migrating from notational-velocity package...')
if !fs.existsSync(prevNoteDirectory)
atom.notifications.addError('notational-velocity.directory #{prevNoteDirectory} does not exists. Migration process is failed.')
unless fs.existsSync(prevNoteDirectory)
atom.notifications.addError("notational-velocity.directory #{prevNoteDirectory} does not exists. Migration process is failed.")
return
if prevNoteDirectory.startsWith(packagesDirectory)
+118
View File
@@ -0,0 +1,118 @@
fs = require 'fs-plus'
path = require 'path'
# Keeps a track of notes and its modified time.
#
# It caches recent notes. Note that a note may partially contain its file content. A file system watcher should call
# `upsert` and `remove` accordingly once the note cache is created.
#
# TODO: Is it a good design? On creation, it deals with file system directly. Once it is created, it relies on outer
# feedback, instead of monitoring the file system by itself.
#
module.exports =
class NoteCache
constructor: (@_baseDirectory, @_maxItem, @_maxNoteLength) ->
@_maxItem = @_maxItem ? 100
@_maxNoteLength = @_maxNoteLength ? 100
@_noteStats = {}
@_state = 'init'
load: (noteStats) ->
@_noteStats = noteStats
@_state = 'cache'
this
ready: ->
@_buildNoteSortedList()
@_buildNoteCache()
@_assert()
@_state = 'ready'
this
toJSON: ->
@_assertReady()
JSON.stringify(@_noteStats)
upsert: (noteId, mtime) ->
@_noteStats[noteId] = mtime.getTime()
return this unless @_state == 'ready'
if !(noteId in @_noteSortedList)
@_noteSortedList.push(noteId)
# TODO: Can this be improved?
@_noteSortedList.sort(@_noteIdCompare)
if @_noteSortedList.indexOf(noteId) < @_maxItem
@_noteCache[noteId] = @_buildNote(noteId)
if @_noteSortedList.length > @_maxItem and @_noteSortedList[@_maxItem] in Object.keys(@_noteCache)
delete @_noteCache[@_noteSortedList[@_maxItem]]
@_assert()
this
remove: (noteId) ->
delete @_noteStats[noteId]
return this unless @_state == 'ready'
if noteId in Object.keys(@_noteCache)
delete @_noteCache[noteId]
if @_noteSortedList.length > @_maxItem and !(@_noteSortedList[@_maxItem] in Object.keys(@_noteCache))
@_noteCache[@_noteSortedList[@_maxItem]] = @_buildNote(@_noteSortedList[@_maxItem])
@_noteSortedList.splice(@_noteSortedList.indexOf(noteId), 1)
@_assert()
this
getNote: (noteId) ->
@_assertReady()
if noteId in @_noteCache then @_noteCache[noteId] else @_buildNote(noteId)
getRecentNotes: ->
@_assertReady()
@_noteSortedList
.slice(0, @_maxItem)
.map((noteId) => @_noteCache[noteId])
hasNoteId: (noteId) ->
@_noteStats.hasOwnProperty(noteId)
getNoteIds: ->
@_noteSortedList ? Object.keys(@_noteStats)
getModifiedAt: (noteId) ->
@_noteStats[noteId]
length: ->
@_assertReady()
@_noteSortedList.length
_assertReady: ->
throw new Error "state is not ready; #{@_state}" unless @_state == 'ready'
_assert: ->
throw new Error "cache length is wrong; #{Object.keys(@_noteCache).length} #{@_noteSortedList} #{@_maxItem}" unless Object.keys(@_noteCache).length == Math.min(@_maxItem, @_noteSortedList.length)
throw new Error 'list length is wrong' unless @_noteSortedList.length == Object.keys(@_noteStats).length
_buildNoteSortedList: ->
@_noteSortedList = Object.keys(@_noteStats)
@_noteSortedList.sort(@_noteIdCompare)
_buildNoteCache: ->
@_noteCache = {}
for noteId in @_noteSortedList.slice(0, @_maxItem)
@_noteCache[noteId] = @_buildNote(noteId)
_buildNote: (noteId) ->
filePath = path.join(@_baseDirectory, noteId)
fileName = path.basename(filePath)
# TODO: Verify it is right
title = path.basename(fileName, path.extname(fileName))
body = ''
if fs.existsSync(filePath)
# TODO: Read just the first n letters.
body = fs.readFileSync(filePath, 'utf8').slice(0, @_maxNoteLength)
return { title: title, body: body, filePath: filePath, modifiedAt: fs.statSync(filePath).mtime }
_noteIdCompare: (a, b) =>
if @_noteStats[a] > @_noteStats[b]
return -1
if @_noteStats[a] < @_noteStats[b]
return 1
return 0
+136
View File
@@ -0,0 +1,136 @@
chokidar = require 'chokidar'
fs = require 'fs'
lunr = require 'lunr'
path = require 'path'
_ = require 'underscore-plus'
zlib = require 'zlib'
{EventEmitter} = require 'events'
NoteCache = require './note-cache'
module.exports =
class NoteWatcher extends EventEmitter
@cacheVersion = 0
constructor: (@_baseDirectory, @_extensions, @_maxItems, @_enableLunrPipeline) ->
@_maxItems = @_maxItems ? 100
@_extensions = @_extensions ? ['.txt', '.md']
@_enableLunrPipeline = @_enableLunrPipeline ? false
@_noteCache = new NoteCache(@_baseDirectory, @_maxItems)
@_restoreSearchIndex() || @_initSearchIndex()
@_initWatcher()
save: ->
return unless @_state == 'ready'
cache = {
baseDirectory: @_baseDirectory,
extensions: @_extensions,
enableLunrPipeline: @_enableLunrPipeline,
version: @_cacheVersion,
searchIndex: JSON.stringify(@_searchIndex),
noteCache: @_noteCache.toJSON(),
}
fs.writeFileSync(
path.join(@_baseDirectory, 'nvatom.cache'),
zlib.deflateSync(JSON.stringify(cache)))
close: ->
@_watcher?.close()
search: (query) ->
if query? and query.length > 0
return @_searchIndex.search(query)
.slice(0, @_maxItems)
.map((x) => @_noteCache.getNote(x.ref))
else
return @_noteCache.getRecentNotes()
length: ->
@_noteCache.length()
_initSearchIndex: ->
@_state = 'initializing'
@_searchIndex = lunr(() ->
@field('title', { boost: 10 })
@field('body')
)
if !@_enableLunrPipeline
@_searchIndex.pipeline.reset()
_restoreSearchIndex: ->
cacheFilePath = path.join(@_baseDirectory, 'nvatom.cache')
return false unless fs.existsSync(cacheFilePath)
cache = JSON.parse(zlib.inflateSync(fs.readFileSync(cacheFilePath)))
return false if cache == null
return false unless _.isEqual(cache.baseDirectory, @_baseDirectory)
return false unless _.isEqual(cache.extensions, @_extensions)
return false unless _.isEqual(cache.enableLunrPipeline, @_enableLunrPipeline)
return false unless _.isEqual(cache.version, @_cacheVersion)
@_searchIndex = lunr.Index.load(JSON.parse(cache.searchIndex))
# TODO: Make this as an interface. Old note cache does not have to know upsert, remove, ready and so on.
# It only needs to know getNoteIds, hasNoteId, and getModifiedAt.
@_oldNoteCache = new NoteCache(@_baseDirectory, @_maxItems).load(JSON.parse(cache.noteCache))
@_state = 'recovering'
return true
_initWatcher: ->
options = {
ignored: (filePath, fileStat) =>
if fileStat?.isFile() then @_extensions.indexOf(path.extname(filePath)) < 0 else false
}
@_watcher = chokidar
.watch @_baseDirectory, options
.on 'add', (args) => @_add(args)
.on 'change', (args) => @_change(args)
.on 'unlink', (args) => @_unlink(args)
.on 'ready', () => @_ready()
_add: (filePath) ->
@_noteCache.upsert(@_toNoteId(filePath), fs.statSync(filePath).mtime)
if @_state != 'recovering'
@_searchIndex.add(@_toFileDetails(filePath))
@emit 'update', filePath
_change: (filePath) ->
@_noteCache.upsert(@_toNoteId(filePath), fs.statSync(filePath).mtime)
if @_state != 'recovering'
@_searchIndex.update(@_toFileDetails(filePath))
@emit 'update', filePath
_unlink: (filePath) ->
@_noteCache.remove(@_toNoteId(filePath))
if @_state != 'recovering'
@_searchIndex.remove(@_toFileDetails(filePath))
@emit 'update', filePath
_ready: () ->
@_noteCache.ready()
if @_state == 'recovering'
@_updateSearchIndex(@_oldNoteCache, @_noteCache)
delete @_oldNoteCache
@_state = 'ready'
@emit 'ready'
_updateSearchIndex: (oldNoteCache, newNoteCache) ->
for noteId in oldNoteCache.getNoteIds()
if !newNoteCache.hasNoteId(noteId)
@_searchIndex.remove(@_toFileDetails(@_toFilePath(noteId)))
else if oldNoteCache.getModifiedAt(noteId) <= newNoteCache.getModifiedAt(noteId)
@_searchIndex.update(@_toFileDetails(@_toFilePath(noteId)))
for noteId in newNoteCache.getNoteIds()
if !oldNoteCache.hasNoteId(noteId)
@_searchIndex.add(@_toFileDetails(@_toFilePath(noteId)))
_toNoteId: (filePath) ->
path.relative(@_baseDirectory, filePath)
_toFilePath: (noteId) ->
path.join(@_baseDirectory, noteId)
_toFileDetails: (filePath) ->
fileName = path.basename(filePath)
return {
id: @_toNoteId(filePath),
title: path.basename(fileName, path.extname(fileName)),
body: if fs.existsSync(filePath) then fs.readFileSync(filePath, { encoding: 'utf8' }) else ''
}
+26
View File
@@ -0,0 +1,26 @@
path = require 'path'
fs = require 'fs-plus'
module.exports =
class Utility
@getNotePath: (title) -> path.join(Utility.getNoteDirectory(), Utility.trim(title) + Utility.getPrimaryNoteExtension())
@getNoteDirectory: -> fs.normalize(atom.config.get('nvatom.directory'))
@getPrimaryNoteExtension: -> if atom.config.get('nvatom.extensions').length then atom.config.get('nvatom.extensions')[0] else '.md'
@isNote: (filePath) ->
return false unless path.extname(filePath) in atom.config.get('nvatom.extensions')
filePath = fs.normalize(filePath)
return true if filePath.startsWith(Utility.getNoteDirectory())
return true if filePath.startsWith(fs.realpathSync(Utility.getNoteDirectory()))
return false unless fs.existsSync(filePath)
filePath = fs.realpathSync(filePath)
return true if filePath.startsWith(Utility.getNoteDirectory())
return true if filePath.startsWith(fs.realpathSync(Utility.getNoteDirectory()))
return false
@trim: (str) -> str?.replace /^\s+|\s+$/g, ''
+4
View File
@@ -9,6 +9,10 @@
'label': 'Toggle'
'command': 'nvatom:toggle'
}
{
'label': 'Open Interlink'
'command': 'nvatom:openInterlink'
}
]
]
}
+15 -9
View File
@@ -1,7 +1,7 @@
{
"name": "nvatom",
"main": "./lib/notational-velocity",
"version": "0.5.1",
"version": "0.11.0",
"private": true,
"contributors": [
"Seongjae Lee <seongjae@gmail.com>",
@@ -9,24 +9,30 @@
"Jonathan Hoyt <hoyt@github.com>"
],
"description": "Notational Velocity for Atom",
"activationCommands": {
"atom-workspace": "nvatom:toggle"
},
"repository": "https://github.com/seongjaelee/notational-velocity",
"keywords": [
"wiki",
"notational velocity",
"nvatom",
"note",
"notetaking"
],
"repository": "https://github.com/seongjaelee/nvatom",
"license": "MIT",
"engines": {
"atom": ">0.50.0"
},
"bugs": {
"url": "https://github.com/seongjaelee/notational-velocity/issues"
"url": "https://github.com/seongjaelee/nvatom/issues"
},
"homepage": "https://github.com/seongjaelee/notational-velocity",
"homepage": "https://github.com/seongjaelee/nvatom",
"dependencies": {
"atom-space-pen-views": "^2.0.3",
"chokidar": "^1.0.5",
"docquery": "^1.1.0",
"fs-plus": "2.x",
"lunr": "^0.7.1",
"underscore-plus": "^1.6.6"
},
"devDependencies": {}
"devDependencies": {
"temp": "0.8.1"
}
}
+105
View File
@@ -0,0 +1,105 @@
fs = require 'fs-plus'
path = require 'path'
temp = require 'temp'
Interlink = require '../lib/interlink'
temp.track()
describe 'Interlink', ->
defaultDirectory = atom.config.get('nvatom.directory')
noteDirectory = null
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
noteDirectory = temp.mkdirSync()
atom.config.set('nvatom.directory', noteDirectory)
waitsForPromise ->
atom.packages.activatePackage('nvatom')
afterEach ->
atom.config.set('nvatom.directory', defaultDirectory)
describe 'when getInterlinkUnderCursor is called', ->
editor = null
describe 'under the note directory', ->
beforeEach ->
waitsForPromise ->
atom.workspace.open(path.join(noteDirectory, 'Interlink.md')).then (o) -> editor = o
it 'returns a trimmed interlink text', ->
testdata = [
{ position: [0, 2], text: '[[Car]]', expected: 'Car' },
{ position: [0, 2], text: '[[Notational Velocity]]', expected: 'Notational Velocity' },
{ position: [0, 2], text: '[[한글 Alphabet Test]]', expected: '한글 Alphabet Test' },
{ position: [0, 2], text: '[[ Car ]]', expected: 'Car' },
{ position: [0, 2], text: '[[Car/Mini]]', expected: 'Car/Mini' },
]
for testitem in testdata
editor.setText testitem.text
editor.setCursorBufferPosition testitem.position
expect(Interlink.getInterlinkUnderCursor(editor)).toBe testitem.expected
it 'returns undefined for invalid text', ->
testdata = [
{ position: [0, 2], text: '[[]]' },
{ position: [0, 3], text: '[[]]' },
{ position: [0, 2], text: '[[ ]]' },
{ position: [0, 1], text: '[Car]' },
{ position: [0, 2], text: '[[[Car]]]' },
{ position: [0, 2], text: '[[Car]' },
{ position: [0, 2], text: '[[Car]]]' },
{ position: [0, 1], text: 'Car' },
]
for testitem in testdata
editor.setText testitem.text
editor.setCursorBufferPosition testitem.position
expect(Interlink.getInterlinkUnderCursor(editor)).toBe undefined
describe 'under a random directory', ->
beforeEach ->
waitsForPromise ->
atom.workspace.open(path.join(temp.mkdirSync(), 'Interlink.md')).then (o) -> editor = o
it 'does not apply the grammar', ->
editor.setText '[[Car]]'
editor.setCursorBufferPosition [0, 2]
expect(Interlink.getInterlinkUnderCursor(editor)).toBe undefined
describe 'when openInterlink is called', ->
describe 'when the editor path is under the note directory', ->
editor = null
beforeEach ->
waitsForPromise ->
atom.workspace.open(path.join(noteDirectory, 'Interlink.md')).then (o) -> editor = o
it 'opens the referred notes', ->
editor.setText '[[Car]]'
editor.setCursorBufferPosition [0, 2]
editorPromise = Interlink.openInterlink()
expect(editorPromise).not.toBe undefined
waitsForPromise ->
editorPromise
runs ->
expect(atom.workspace.getActiveTextEditor().getPath().endsWith('Car.md')).toBe true
describe 'when the editor path is not under the note directory', ->
editor = null
beforeEach ->
waitsForPromise ->
atom.workspace.open(path.join(temp.mkdirSync(), 'Interlink.md')).then (o) -> editor = o
it 'does nothing', ->
editor.setText '[[Car]]'
editor.setCursorBufferPosition [0, 2]
editorPromise = Interlink.openInterlink()
expect(editorPromise).toBe undefined
+21 -21
View File
@@ -1,4 +1,7 @@
path = require 'path'
temp = require 'temp'
temp.track()
describe "nvAtom", ->
defaultDirectory = atom.config.get('nvatom.directory')
@@ -7,42 +10,39 @@ describe "nvAtom", ->
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
activationPromise = atom.packages.activatePackage('nvatom')
atom.config.set('nvatom.directory', 'testdata')
afterEach ->
atom.config.set('nvatom.directory', defaultDirectory)
describe "when the nvatom:toggle event is triggered", ->
it "attaches and then detaches the view", ->
expect(workspaceElement.querySelector('.nvatom')).not.toExist()
# This is an activation event, triggering it will cause the package to be activated.
atom.commands.dispatch workspaceElement, 'nvatom:toggle'
noteDirectory = path.join(temp.mkdirSync())
atom.config.set('nvatom.directory', noteDirectory)
waitsForPromise ->
activationPromise
atom.packages.activatePackage('nvatom')
runs ->
expect(workspaceElement.querySelector('.nvatom')).toExist()
expect(workspaceElement.querySelector('.nvatom')).not.toExist()
atom.commands.dispatch workspaceElement, 'nvatom:toggle'
expect(workspaceElement.querySelector('.nvatom')).toExist()
expect(workspaceElement.querySelector('.nvatom').parentNode.style.display).not.toBe 'none'
atom.commands.dispatch workspaceElement, 'nvatom:toggle'
expect(workspaceElement.querySelector('.nvatom').parentNode.style.display).toBe 'none'
it "checks if we banned the default directory under packages directory", ->
atom.notifications.clear()
noteDirectory = path.join(process.env.ATOM_HOME, 'packages', 'nvatom', 'notebook')
atom.config.set('nvatom.directory', noteDirectory)
waitsForPromise ->
atom.packages.activatePackage('notifications')
atom.packages.activatePackage('nvatom')
runs ->
noteDirectoryUnderPackageDirectory = path.join(process.env.ATOM_HOME, 'packages', 'nvatom', 'notebook')
atom.config.set('nvatom.directory', noteDirectoryUnderPackageDirectory)
# This is an activation event, triggering it will cause the package to be activated.
atom.commands.dispatch workspaceElement, 'nvatom:toggle'
waitsForPromise ->
activationPromise
atom.packages.activatePackage('notifications')
runs ->
notificationContainer = workspaceElement.querySelector('atom-notifications')
notification = notificationContainer.querySelector('atom-notification.fatal')
+188
View File
@@ -0,0 +1,188 @@
fs = require 'fs'
path = require 'path'
temp = require 'temp'
NoteCache = require '../lib/note-cache'
temp.track()
String::repeat = (n) -> Array(n+1).join(this)
describe 'note cache', ->
cache = null
directoryPath = null
beforeEach ->
directoryPath = temp.mkdirSync()
fs.writeFileSync(path.join(directoryPath, 'foo.md'), 'foo'.repeat(100))
fs.utimesSync(path.join(directoryPath, 'foo.md'), 0, 1)
fs.writeFileSync(path.join(directoryPath, 'bar.md'), 'bar'.repeat(100))
fs.utimesSync(path.join(directoryPath, 'bar.md'), 0, 2)
fs.mkdirSync(path.join(directoryPath, 'baz'))
fs.writeFileSync(path.join(directoryPath, 'baz', 'baz.md'), 'baz'.repeat(100))
fs.utimesSync(path.join(directoryPath, 'baz', 'baz.md'), 0, 3)
cache = new NoteCache(directoryPath, 2, 100)
.upsert('foo.md', fs.statSync(path.join(directoryPath, 'foo.md')).mtime)
.upsert(path.join('baz', 'baz.md'), fs.statSync(path.join(directoryPath, 'baz/baz.md')).mtime)
.upsert('bar.md', fs.statSync(path.join(directoryPath, 'bar.md')).mtime)
.ready()
it 'getNoteIds', ->
noteIds = cache.getNoteIds()
for noteId in ['foo.md', 'bar.md', 'baz/baz.md']
expect(noteIds).toContain(noteId)
expect(cache.length()).toBe(3)
it 'getRecentNotes', ->
notes = cache.getRecentNotes()
expect(notes.length).toBe(2)
expect(notes[0].title).toBe('baz')
expect(notes[1].title).toBe('bar')
it 'getNote', ->
expect(cache.getNote('baz/baz.md').title).toBe('baz')
expect(cache.getNote('bar.md').title).toBe('bar')
expect(cache.getNote('foo.md').title).toBe('foo')
it 'hasNoteId', ->
expect(cache.hasNoteId('baz/baz.md')).toBe(true)
expect(cache.hasNoteId('bar.md')).toBe(true)
expect(cache.hasNoteId('foo.md')).toBe(true)
expect(cache.hasNoteId('baz.md')).toBe(false)
it 'getModifiedAt', ->
expect(cache.getModifiedAt('baz/baz.md')).toBe(3000)
expect(cache.getModifiedAt('bar.md')).toBe(2000)
expect(cache.getModifiedAt('foo.md')).toBe(1000)
it 'adds a new note', ->
fs.writeFileSync(path.join(directoryPath, 'moo.md'), 'moo'.repeat(100))
fs.utimesSync(path.join(directoryPath, 'moo.md'), 0, 4)
cache.upsert('moo.md', fs.statSync(path.join(directoryPath, 'moo.md')).mtime)
expect(cache.length()).toBe(4)
expect(cache.hasNoteId('moo.md')).toBe(true)
expect(cache.getModifiedAt('moo.md')).toBe(4000)
expect(cache.getRecentNotes().length).toBe(2)
expect(cache.getRecentNotes()[0].title).toBe('moo')
expect(cache.getNote('moo.md').title).toBe('moo')
it 'updates a note not in the recent notes', ->
fs.writeFileSync(path.join(directoryPath, 'foo.md'), 'foo'.repeat(2))
fs.utimesSync(path.join(directoryPath, 'foo.md'), 0, 4)
cache.upsert('foo.md', fs.statSync(path.join(directoryPath, 'foo.md')).mtime)
expect(cache.length()).toBe(3)
expect(cache.hasNoteId('foo.md')).toBe(true)
expect(cache.getModifiedAt('foo.md')).toBe(4000)
expect(cache.getRecentNotes().length).toBe(2)
expect(cache.getRecentNotes()[0].title).toBe('foo')
expect(cache.getNote('foo.md').title).toBe('foo')
it 'updates a note in the recent notes', ->
fs.writeFileSync(path.join(directoryPath, 'bar.md'), 'bar'.repeat(2))
fs.utimesSync(path.join(directoryPath, 'bar.md'), 0, 4)
cache.upsert('bar.md', fs.statSync(path.join(directoryPath, 'bar.md')).mtime)
expect(cache.length()).toBe(3)
expect(cache.hasNoteId('bar.md')).toBe(true)
expect(cache.getModifiedAt('bar.md')).toBe(4000)
expect(cache.getRecentNotes().length).toBe(2)
expect(cache.getRecentNotes()[0].title).toBe('bar')
expect(cache.getNote('bar.md').title).toBe('bar')
it 'deletes a note not in the recent notes', ->
fs.unlinkSync(path.join(directoryPath, 'foo.md'))
cache.remove('foo.md')
expect(cache.length()).toBe(2)
expect(cache.hasNoteId('foo.md')).toBe(false)
expect(cache.getRecentNotes().length).toBe(2)
expect(cache.getRecentNotes()[0].title).toBe('baz')
expect(cache.getRecentNotes()[1].title).toBe('bar')
it 'deletes a note in the recent notes', ->
cache.remove('bar.md')
expect(cache.length()).toBe(2)
expect(cache.hasNoteId('bar.md')).toBe(false)
expect(cache.getRecentNotes().length).toBe(2)
expect(cache.getRecentNotes()[0].title).toBe('baz')
expect(cache.getRecentNotes()[1].title).toBe('foo')
it 'deletes two notes', ->
cache.remove('bar.md')
cache.remove('foo.md')
expect(cache.length()).toBe(1)
expect(cache.hasNoteId('foo.md')).toBe(false)
expect(cache.hasNoteId('bar.md')).toBe(false)
expect(cache.getRecentNotes().length).toBe(1)
expect(cache.getRecentNotes()[0].title).toBe('baz')
# describe 'note cache', ->
# cache = null
# directoryPath = null
#
# beforeEach ->
# directoryPath = temp.mkdirSync()
#
# it 'handles cyclic symlink directories', ->
# fs.symlinkSync(directoryPath, path.join(directoryPath, 'foo'))
# fs.writeFileSync(path.join(directoryPath, 'foo.md'), 'foo'.repeat(100))
# cache = new NoteCache(directoryPath, ['.md'], 2, 100)
# expect(cache.length()).toBe(1)
#
# it 'handles symlink notes', ->
# # TODO: this behavior should be same for chokidar.
# anotherDirectoryPath = temp.mkdirSync()
# fs.writeFileSync(path.join(anotherDirectoryPath, 'foo.md'), 'foo'.repeat(100))
# fs.symlinkSync(path.join(anotherDirectoryPath, 'foo.md'), path.join(directoryPath, 'foo.md'))
# fs.symlinkSync(path.join(anotherDirectoryPath, 'foo.md'), path.join(directoryPath, 'bar.md'))
# fs.symlinkSync(anotherDirectoryPath, path.join(directoryPath, 'bar'))
# cache = new NoteCache(directoryPath, ['.md'], 2, 100)
# # foo.md, bar.md, bar/foo.md
# expect(cache.length()).toBe(3)
describe 'note cache', ->
cache = null
directoryPath = null
beforeEach ->
directoryPath = temp.mkdirSync()
cache = new NoteCache(directoryPath, 2, 100)
.ready()
fs.writeFileSync(path.join(directoryPath, 'foo.md'), 'foo'.repeat(100))
fs.utimesSync(path.join(directoryPath, 'foo.md'), 0, 1)
fs.writeFileSync(path.join(directoryPath, 'bar.md'), 'bar'.repeat(100))
fs.utimesSync(path.join(directoryPath, 'bar.md'), 0, 2)
fs.mkdirSync(path.join(directoryPath, 'baz'))
fs.writeFileSync(path.join(directoryPath, 'baz', 'baz.md'), 'baz'.repeat(100))
fs.utimesSync(path.join(directoryPath, 'baz', 'baz.md'), 0, 3)
cache
.upsert('foo.md', fs.statSync(path.join(directoryPath, 'foo.md')).mtime)
.upsert('bar.md', fs.statSync(path.join(directoryPath, 'bar.md')).mtime)
.upsert(path.join('baz', 'baz.md'), fs.statSync(path.join(directoryPath, 'baz', 'baz.md')).mtime)
it 'has all note ids', ->
noteIds = cache.getNoteIds()
for noteId in ['foo.md', 'bar.md', 'baz/baz.md']
expect(noteIds).toContain(noteId)
expect(cache.length()).toBe(3)
it 'getRecentNotes', ->
notes = cache.getRecentNotes()
expect(notes.length).toBe(2)
expect(notes[0].title).toBe('baz')
expect(notes[1].title).toBe('bar')
it 'getNote', ->
expect(cache.getNote('baz/baz.md').title).toBe('baz')
expect(cache.getNote('bar.md').title).toBe('bar')
expect(cache.getNote('foo.md').title).toBe('foo')
it 'getModifiedAt', ->
expect(cache.getModifiedAt('baz/baz.md')).toBe(3000)
expect(cache.getModifiedAt('bar.md')).toBe(2000)
expect(cache.getModifiedAt('foo.md')).toBe(1000)
+143
View File
@@ -0,0 +1,143 @@
fs = require 'fs'
path = require 'path'
temp = require 'temp'
NoteWatcher = require '../lib/note-watcher'
temp.track()
describe 'note-watcher', ->
watcher = null
directoryPath = null
beforeEach ->
directoryPath = temp.mkdirSync()
fs.writeFileSync(
path.join(directoryPath, 'foo.md'),
'The use of foo in a programming context is generally credited to the Tech Model Railroad Club (TMRC) of MIT.')
fs.writeFileSync(
path.join(directoryPath, 'bar.md'),
'When used in connection with bar it is generally traced to the World War II military slang FUBAR.')
spy = jasmine.createSpy()
watcher = new NoteWatcher(directoryPath)
watcher.on 'ready', spy
waitsFor -> spy.wasCalled
afterEach ->
watcher.close()
it 'handles a symbolic link note properly', ->
spy = jasmine.createSpy()
watcher.on 'update', spy
anotherDirectoryPath = temp.mkdirSync()
fs.writeFileSync(path.join(anotherDirectoryPath, 'baz.md'), 'A common name for the foobar, also foobaz.')
fs.symlinkSync(path.join(anotherDirectoryPath, 'baz.md'), path.join(directoryPath, 'baz.md'))
waitsFor -> spy.wasCalled
runs ->
result = watcher.search('baz')
expect(result.length).toBe(1)
it 'handles a symbolic link directory properly', ->
spy = jasmine.createSpy()
watcher.on 'update', spy
anotherDirectoryPath = temp.mkdirSync()
fs.writeFileSync(path.join(anotherDirectoryPath, 'baz.md'), 'A common name for the foobar, also foobaz.')
fs.symlinkSync(anotherDirectoryPath, path.join(directoryPath, 'baz'))
waitsFor -> spy.wasCalled
runs ->
result = watcher.search('baz')
expect(result.length).toBe(1)
expect(result[0].filePath).toBe(path.join(directoryPath, 'baz', 'baz.md'))
it 'handles paired symbolic links properly without falling into an infinite loop', ->
spy = jasmine.createSpy()
watcher.on 'update', spy
anotherDirectoryPath = temp.mkdirSync()
fs.writeFileSync(path.join(anotherDirectoryPath, 'baz.md'), 'A common name for the foobar, also foobaz.')
fs.symlinkSync(anotherDirectoryPath, path.join(directoryPath, 'bazDir'))
fs.symlinkSync(directoryPath, path.join(anotherDirectoryPath, 'fooDir'))
waitsFor -> spy.wasCalled
runs ->
result = watcher.search('baz')
expect(result.length).toBe(1)
expect(result[0].filePath).toBe(path.join(directoryPath, 'bazDir', 'baz.md'))
it 'watches adding a file', ->
spy = jasmine.createSpy()
watcher.on 'update', spy
fs.writeFileSync(path.join(directoryPath, 'note.md'), 'hello world')
waitsFor -> spy.wasCalled
it 'watches changing a file', ->
spy = jasmine.createSpy()
watcher.on 'update', spy
filePath = path.join(directoryPath, 'note.md')
fs.writeFileSync(filePath, 'hello world')
waitsFor -> spy.callCount == 1
runs -> fs.writeFileSync(filePath, 'hello world 2')
waitsFor -> spy.callCount == 2
it 'watches removing a file', ->
spy = jasmine.createSpy()
watcher.on('update', spy)
filePath = path.join(directoryPath, 'note.md')
fs.writeFileSync(filePath, 'hello world')
waitsFor -> spy.callCount == 1
runs -> fs.unlinkSync(filePath)
waitsFor -> spy.callCount == 2
it 'searches', ->
result = watcher.search('programming')
expect(result.length).toBe(1)
expect(result[0].title).toBe('foo')
it 'saves and loads', ->
watcher.save()
watcher.close()
watcher = new NoteWatcher(directoryPath)
spy = jasmine.createSpy()
watcher.on 'ready', spy
waitsFor -> spy.wasCalled
runs ->
result = watcher.search('programming')
expect(result.length).toBe(1)
expect(result[0].title).toBe('foo')
it 'loads the modified contents correctly', ->
watcher.save()
watcher.close()
# create, change, and delete
fs.writeFileSync(path.join(directoryPath, 'baz.md'), 'A common name for the foobar, also foobaz.')
fs.writeFileSync(path.join(directoryPath, 'foo.md'), 'The etymology of foo is obscure.')
fs.unlinkSync(path.join(directoryPath, 'bar.md'))
watcher = new NoteWatcher(directoryPath)
spy = jasmine.createSpy()
watcher.on('ready', spy)
waitsFor -> spy.wasCalled
runs ->
# in deleted foo.md
expect(watcher.search('programming').length).toBe(0)
# in replaced foo.md
expect(watcher.search('obscure').length).toBe(1)
# in bar.md
expect(watcher.search('connection').length).toBe(0)
# in baz.md
expect(watcher.search('baz').length).toBe(1)
+54
View File
@@ -0,0 +1,54 @@
fs = require 'fs-plus'
path = require 'path'
temp = require 'temp'
Utility = require '../lib/utility'
temp.track()
describe "utility", ->
defaultNoteDirectory = atom.config.get('nvatom.directory')
defaultNoteExtensions = atom.config.get('nvatom.extensions')
afterEach ->
atom.config.set('nvatom.directory', defaultDirectory)
atom.config.set('nvatom.extensions', defaultNoteExtensions)
describe 'trim', ->
expect(Utility.trim(null)).toBe(undefined)
expect(Utility.trim(undefined)).toBe(undefined)
expect(Utility.trim('')).toBe('')
expect(Utility.trim(' ')).toBe('')
expect(Utility.trim(' hello world ')).toBe('hello world')
expect(Utility.trim(' hello world\t\n\r ')).toBe('hello world')
describe 'getPrimaryNoteExtension', ->
atom.config.set('nvatom.extensions', ['.md', '.markdown'])
expect(Utility.getPrimaryNoteExtension()).toBe('.md')
atom.config.set('nvatom.extensions', ['.markdown'])
expect(Utility.getPrimaryNoteExtension()).toBe('.markdown')
atom.config.set('nvatom.extensions', [])
expect(Utility.getPrimaryNoteExtension()).toBe('.md')
describe 'isNote handles symlinks correctly', ->
atom.config.set('nvatom.extensions', ['.md', '.markdown'])
tempDirectoryPath = path.join(temp.mkdirSync())
noteDirectoryPath = path.join(temp.mkdirSync())
noteDirectoryPathSymlink = path.join(tempDirectoryPath, 'note book')
notePath = path.join(noteDirectoryPath, 'note.md')
notePathSymlink = path.join(noteDirectoryPathSymlink, 'note symlink.md')
fs.writeFileSync(notePath, 'dummy')
fs.symlinkSync(noteDirectoryPath, noteDirectoryPathSymlink)
fs.symlinkSync(notePath, notePathSymlink)
expect(fs.existsSync(notePath)).toBe(true)
expect(fs.existsSync(fs.normalize(notePath))).toBe(true)
atom.config.set('nvatom.directory', noteDirectoryPath)
expect(Utility.isNote(notePath)).toBe(true)
expect(Utility.isNote(notePathSymlink)).toBe(true)
atom.config.set('nvatom.directory', noteDirectoryPathSymlink)
expect(Utility.isNote(notePath)).toBe(true)
expect(Utility.isNote(notePathSymlink)).toBe(true)