22 Commits
Author SHA1 Message Date
Seongjae Lee 52937e2f3c Prepare 0.2.0 release 2015-09-08 23:22:13 -07:00
Seongjae Lee 55631b89ea Update changelog and contributors 2015-09-08 23:20:28 -07:00
Seongjae Lee 7ae6d1c211 Fix the incompatible native module test failure 2015-09-08 22:56:57 -07:00
Seongjae Lee 56821bccbc First attempt with DocQuery 2015-08-30 21:52:20 -07:00
Seong Jae Lee 11a6c37c70 Revert to 03654ac, since the current one gives an error on OSX 2015-08-28 23:41:41 -04:00
Seongjae Lee ae435d7043 Merge pull request #24 from jonmagic/powered-by-docquery
First attempt with DocQuery
2015-08-28 23:18:36 -04:00
Seongjae Lee 03654ac2c6 Merge pull request #16 from garthk/fix-settings-documentation
Fix settings documentation
2015-05-28 12:11:36 -07:00
Garth Kidd 91f4bca077 Extract indentation advice to subsequent paragraph. 2015-05-27 16:28:38 +10:00
Garth Kidd e39334f9fa "via" is somewhat quaint. 2015-05-27 16:21:01 +10:00
Garth Kidd d8e60f9cfc Assert CSON format. 2015-05-27 16:18:29 +10:00
Garth Kidd 95a793f050 Fix settings docs: seongjaelee/notational-velocity#15 2015-05-27 15:31:17 +10:00
Jonathan Hoyt c15e7e0f96 First attempt with DocQuery 2015-05-24 23:09:50 -07:00
Seongjae Lee 3679cf0a71 Fix #14 so that it can create lowercase/uppercase notes as the user wants 2015-05-16 22:49:55 -07:00
Seongjae Lee 6207b379eb Avoid the user of WorkspaceView in tests 2015-05-09 15:41:16 -07:00
Seongjae Lee 66e03ce0e3 Fix a failing test due to a missing dependency 2015-05-09 15:11:16 -07:00
Seongjae Lee f19c613204 Merge pull request #11 from jonmagic/alternate-keybinding-instructions
Add instructions for changing the cmd-l keybinding
2015-05-09 15:02:57 -07:00
Seongjae Lee 42eb70ac27 Merge pull request #10 from jonmagic/auto-save-on-did-stop-changing
Auto save document 1 second after finishing typing
2015-05-09 15:02:46 -07:00
Jonathan Hoyt a2b81ad570 Add instructions for changing the cmd-l keybinding 2015-05-09 14:47:13 -07:00
Jonathan Hoyt 1f91f4d6b5 Remove console.log 2015-05-09 14:34:49 -07:00
Jonathan Hoyt 0f269c9216 Debounce the save so the editor doesn't slow down 2015-05-09 14:27:21 -07:00
Jonathan Hoyt e94430630c Save in editor.onDidStopChanging
Temporarily disables whitespace package so that you can create
newlines without getting them removed on autosave.
2015-05-09 13:49:47 -07:00
Seongjae Lee 49c081ed6e Introduce autosave feature
Fix #8.
2015-04-27 22:24:43 -07:00
11 changed files with 132 additions and 328 deletions
+4
View File
@@ -1,2 +1,6 @@
## 0.2.0
- Use DocQuery
- Introduce autosave features
## 0.1.0
- First release
+24 -1
View File
@@ -26,12 +26,35 @@ We do believe Notational Velocity is the precursor of the famous note-taking app
## Settings
- `Note Directory`: The directory to archive notes.
To configure your note directory, set `notational-velocity.directory`:
* Open your `~/.atom/config.cson` file from the menu: *Atom > Open Your Config*
* Append the following lines:
```cson
'notational-velocity':
directory: '/path/to/your/notes'
```
The first line should be indented by one step from `*` at the top. If you've
kept the default indentation of two spaces, the block above should paste in
properly.
Double-quotes also work.
## Key Bindings
- `alt-cmd-l`: Toggles the search view.
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:
```cson
'atom-text-editor':
'cmd-l': 'unset!'
'atom-workspace':
'cmd-l': 'notational-velocity:toggle'
```
## References
- [Notational Velocity](http://notational.net/)
+58 -54
View File
@@ -1,69 +1,55 @@
path = require 'path'
fs = require 'fs-plus'
_ = require 'underscore-plus'
{$, $$, SelectListView} = require 'atom-space-pen-views'
NoteDirectory = require './note-directory'
Note = require './note'
DocQuery = require 'DocQuery'
module.exports =
class NotationalVelocityView extends SelectListView
initialize: ->
initialize: (state) ->
@initializedAt = new Date()
super
@addClass('notational-velocity from-top overlay')
@rootDirectory = atom.config.get('notational-velocity.directory')
if !fs.existsSync(@rootDirectory)
throw new Error("The given directory #{@rootDirectory} does not exist. "
+ "Set the note directory to the existing one from Settings.")
@noteDirectory = new NoteDirectory(@rootDirectory, null, () => @updateNotes())
@updateNotes()
@prevFilterQuery = ''
@prevCursorPosition = 0
updateNotes: () ->
@notes = @noteDirectory.getNotes()
@setItems(@notes)
@documentsLoaded = false
@docQuery = new DocQuery(@rootDirectory, {recursive: true})
@docQuery.on "ready", () =>
@documentsLoaded = true
@setLoading()
@populateList()
@docQuery.on "added", (fileDetails) =>
@populateList() if @documentsLoaded
@docQuery.on "updated", (fileDetails) =>
@populateList() if @documentsLoaded
@docQuery.on "removed", (fileDetails) =>
@populateList() if @documentsLoaded
selectItem: (filterQuery) ->
if filterQuery.length == 0
@prevCursorPosition = 0
return null
titlePatterns = [
///^#{filterQuery}$///i,
///^#{filterQuery}///i,
]
titleItem = null
for titlePattern in titlePatterns
titleItems = @notes
.filter (x) -> x.getTitle().match(titlePattern) != null
titleItem = if titleItems.length > 0 then titleItems[0] else null
if titleItem != null
break
titleItem = @docQuery.search(filterQuery)[0]
# If title item is not null, auto-fill the search panel.
# But we don't want to fill it when deleting.
editor = @filterEditorView.model
currCursorPosition = editor.getCursorBufferPosition().column
if titleItem != null && @prevCursorPosition < currCursorPosition
@prevFilterQuery = titleItem.getTitle()
editor.setText(titleItem.getTitle())
editor.selectLeft(titleItem.getTitle().length - filterQuery.length)
if titleItem != undefined && @prevCursorPosition < currCursorPosition
@prevFilterQuery = titleItem.title
editor.setText(filterQuery + titleItem.title.slice(filterQuery.length))
editor.selectLeft(titleItem.title.length - filterQuery.length)
@prevCursorPosition = currCursorPosition
return titleItem
filter: (filterQuery) ->
if filterQuery.length == 0
return @notes
queries = filterQuery.split(' ')
.filter (x) -> x.length > 0
.map (x) -> new RegExp(x, 'gi')
return @notes
.filter (x) ->
queries
.map (q) -> q.test(x.getText()) || q.test(x.getTitle())
.reduce (x, y) -> x && y
return @docQuery.search(filterQuery)
getFilterKey: ->
'filetext'
@@ -71,32 +57,47 @@ class NotationalVelocityView extends SelectListView
toggle: ->
if @panel?.isVisible()
@hide()
else
else if @documentsLoaded
@populateList()
@show()
else
@setLoading("Loading documents")
@show()
viewForItem: (item) ->
content = item.getText()[0...100]
content = item.body[0...100]
$$ ->
@li class: 'two-lines', =>
@div class: 'primary-line', =>
@span "#{item.getTitle()}"
@div class: 'metadata', "#{item.getModified().toLocaleDateString()}"
@span "#{item.title}"
@div class: 'metadata', "#{item.modifiedAt.toLocaleDateString()}"
@div class: 'secondary-line', "#{content}"
confirmSelection: ->
item = @getSelectedItem()
item = @getSelectedItem()
filePath = null
sanitizedQuery = @getFilterQuery().replace(/\s+$/, '')
calculatedPath = path.join(@rootDirectory, sanitizedQuery + '.md')
if item?
atom.workspace.open(item.getFilePath())
@cancel()
else
sanitizedQuery = @getFilterQuery().replace(/\s+$/, '')
if sanitizedQuery.length > 0
filePath = path.join(@rootDirectory, sanitizedQuery + '.md')
fs.writeFileSync(filePath, '')
atom.workspace.open(filePath)
@cancel()
filePath = item.filePath
else if fs.existsSync(calculatedPath)
filePath = calculatedPath
else if sanitizedQuery.length > 0
filePath = calculatedPath
fs.writeFileSync(filePath, '')
if filePath
atom.workspace.open(filePath).then (editor) ->
save = ->
atom.packages.deactivatePackage 'whitespace'
editor.save()
atom.packages.activatePackage 'whitespace'
debouncedSave = _.debounce save, 1000
editor.onDidStopChanging () ->
debouncedSave() if editor.isModified()
@cancel()
destroy: ->
@cancel()
@@ -115,10 +116,13 @@ class NotationalVelocityView extends SelectListView
@panel?.hide()
populateList: ->
return unless @notes?
filterQuery = @getFilterQuery()
filteredItems = @filter(filterQuery)
filteredItems = null
if filterQuery == "" || filterQuery == undefined
filteredItems = @docQuery.documents
else
filteredItems = @filter(filterQuery)
selectedItem = @selectItem(filterQuery)
@list.empty()
@@ -136,7 +140,7 @@ class NotationalVelocityView extends SelectListView
@selectItemView(@list.find("li:nth-child(#{n})"))
else
@setError(@getEmptyMessage(@notes.length, filteredItems.length))
@setError(@getEmptyMessage(@docQuery.documents.length, filteredItems.length))
schedulePopulateList: ->
# We can skip it when we are just moving the position of the cursor.
+31 -2
View File
@@ -1,4 +1,6 @@
{CompositeDisposable} = require 'atom'
path = require 'path'
fs = require 'fs-plus'
{CompositeDisposable, Disposable} = require 'atom'
module.exports =
config:
@@ -11,6 +13,8 @@ module.exports =
notationalVelocityView: null
activate: (state) ->
@rootDirectory = fs.realpathSync(atom.config.get('notational-velocity.directory'))
# Events subscribed to in atom's system can be easily cleaned up with a
# CompositeDisposable
@subscriptions = new CompositeDisposable
@@ -19,6 +23,20 @@ module.exports =
@subscriptions.add atom.commands.add 'atom-workspace',
'notational-velocity:toggle': => @createView(state).toggle()
handleBeforeUnload = @autosaveAll.bind(this)
window.addEventListener('beforeunload', handleBeforeUnload, true)
@subscriptions.add new Disposable -> window.removeEventListener('beforeunload', handleBeforeUnload, true)
handleBlur = (event) =>
if event.target is window
@autosaveAll()
else if event.target.matches('atom-text-editor:not([mini])') and not event.target.contains(event.relatedTarget)
@autosave(event.target.getModel())
window.addEventListener('blur', handleBlur, true)
@subscriptions.add new Disposable -> window.removeEventListener('blur', handleBlur, true)
@subscriptions.add atom.workspace.onWillDestroyPaneItem ({item}) => @autosave(item)
deactivate: ->
@subscriptions.dispose()
@notationalVelocityView.destroy()
@@ -26,8 +44,19 @@ module.exports =
serialize: ->
notationalVelocityViewState: @notationalVelocityView.serialize()
createView: (state) ->
createView: (state, docQuery) ->
unless @notationalVelocityView?
NotationalVelocityView = require './notational-velocity-view'
@notationalVelocityView = new NotationalVelocityView(state.notationalVelocityViewState)
@notationalVelocityView
autosave: (paneItem) ->
return unless paneItem?.getURI?()?
return unless paneItem?.isModified?()
uri = paneItem.getURI()
return unless uri.indexOf(@rootDirectory) == 0
return unless fs.isMarkdownExtension(path.extname(uri))
paneItem?.save?()
autosaveAll: ->
@autosave(paneItem) for paneItem in atom.workspace.getPaneItems()
-58
View File
@@ -1,58 +0,0 @@
path = require 'path'
fs = require 'fs-plus'
pathWatcher = require 'pathwatcher'
Note = require './note'
module.exports =
class NoteDirectory
constructor: (@filePath, @parent, @onChangeCallback) ->
@directories = []
@notes = []
@updateMetadata()
@watcher = pathWatcher.watch(@filePath, (event) => @onChange(event))
destroy: ->
@notes.map (x) -> x.destroy()
@directories.map (x) -> x.destroy()
@watcher.close()
updateMetadata: ->
@notes.map (x) -> x.destroy()
@directories.map (x) -> x.destroy()
@directories = []
@notes = []
try
filenames = fs.readdirSync(@filePath)
catch e
return
for filename in filenames
@addChild(path.join(@filePath, filename))
addChild: (filePath) ->
try
fileStat = fs.statSync(filePath)
catch e
return
if fileStat.isDirectory()
@directories.push(new NoteDirectory(filePath, this, @onChangeCallback))
else
if fs.isMarkdownExtension(path.extname(filePath))
@notes.push(new Note(filePath, this, @onChangeCallback))
getNotes: ->
ret = []
ret = ret.concat(@notes)
for directory in @directories
ret = ret.concat(directory.getNotes())
if @parent is null
ret.sort (x, y) -> if x.getModified().getTime() <= y.getModified().getTime() then 1 else -1
return ret
onChange: (event) ->
# For the case of rename and change, it will be handled in its parent.
if event == 'change'
@updateMetadata()
if @onChangeCallback != null
@onChangeCallback()
-37
View File
@@ -1,37 +0,0 @@
path = require 'path'
fs = require 'fs'
pathWatcher = require 'pathwatcher'
module.exports =
class Note
constructor: (@filePath, @parent, @onChangeCallback) ->
@updateMetadata()
@updateText()
@watcher = pathWatcher.watch(@filePath, (event) => @onChange(event))
destroy: ->
@watcher.close()
updateMetadata: ->
@modified = fs.statSync(@filePath).mtime
relativePath = path.relative(atom.config.get('notational-velocity.directory'), @filePath)
@title = path.join(
path.dirname(relativePath),
path.basename(relativePath, path.extname(relativePath))
)
updateText: ->
@text = fs.readFileSync(@filePath, 'utf8')
onChange: (event) ->
# For the case of rename and change, it will be handled in its parent.
if event == 'change' && fs.existsSync(@filePath)
@updateMetadata()
@updateText()
if @onChangeCallback != null
@onChangeCallback()
getTitle: -> @title
getText: -> @text
getModified: -> @modified
getFilePath: -> @filePath
+9 -10
View File
@@ -1,10 +1,12 @@
{
"name": "notational-velocity",
"main": "./lib/notational-velocity",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"contributors": [
"Seongjae Lee <seongjaelee@gmail.com>"
"Seongjae Lee <seongjae@gmail.com>",
"Nikita Litvin <deltaidea@derpy.ru>",
"Jonathan Hoyt <hoyt@github.com>"
],
"description": "Notational Velocity for Atom",
"activationCommands": {
@@ -20,14 +22,11 @@
},
"homepage": "https://github.com/seongjaelee/notational-velocity",
"dependencies": {
"fs-plus": "2.x",
"atom-space-pen-views": "^2.0.3",
"pathwatcher": "^4.2"
"chokidar": "^1.0.5",
"docquery": "^1.1.0",
"fs-plus": "2.x",
"underscore-plus": "^1.6.6"
},
"devDependencies": {
"fs-plus": "2.x",
"atom-space-pen-views": "^2.0.3",
"pathwatcher": "^4.2",
"temp": "~0.7.0"
}
"devDependencies": {}
}
+6 -8
View File
@@ -1,6 +1,3 @@
{WorkspaceView} = require 'atom'
NotationalVelocity = require '../lib/notational-velocity'
# Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
#
# To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit`
@@ -9,9 +6,10 @@ NotationalVelocity = require '../lib/notational-velocity'
describe "NotationalVelocity", ->
defaultDirectory = atom.config.get('notational-velocity.directory')
activationPromise = null
workspaceElement = null
beforeEach ->
atom.workspaceView = new WorkspaceView
workspaceElement = atom.views.getView(atom.workspace)
activationPromise = atom.packages.activatePackage('notational-velocity')
atom.config.set('notational-velocity.directory', 'testdata')
@@ -20,15 +18,15 @@ describe "NotationalVelocity", ->
describe "when the notational-velocity:toggle event is triggered", ->
it "attaches and then detaches the view", ->
expect(atom.workspaceView.find('.notational-velocity')).not.toExist()
expect(workspaceElement.querySelector('.notational-velocity')).not.toExist()
# This is an activation event, triggering it will cause the package to be
# activated.
atom.commands.dispatch atom.workspaceView.element, 'notational-velocity:toggle'
atom.commands.dispatch workspaceElement, 'notational-velocity:toggle'
waitsForPromise ->
activationPromise
runs ->
expect(atom.workspaceView.find('.notational-velocity')).toExist()
atom.commands.dispatch atom.workspaceView.element, 'notational-velocity:toggle'
expect(workspaceElement.querySelector('.notational-velocity')).toExist()
atom.commands.dispatch workspaceElement, 'notational-velocity:toggle'
@@ -1,5 +0,0 @@
NotationalVelocityView = require '../lib/notational-velocity-view'
describe "NotationalVelocityView", ->
it "has one valid test", ->
expect("life").toBe "life"
-117
View File
@@ -1,117 +0,0 @@
path = require 'path'
fs = require 'fs-plus'
temp = require 'temp'
pathWatcher = require 'pathwatcher'
NoteDirectory = require '../lib/note-directory'
Note = require '../lib/note'
describe 'NoteDirectory.getNotes', ->
defaultDirectory = atom.config.get('notational-velocity.directory')
tempDirectory = temp.mkdirSync('node-pathwatcher-directory')
noteDirectory = null
isCallbackCalled = false
# We don't want to let the mtimes of two consecutively create files are same.
# This function ensures the mtime of the file created before calling this function to be always
# smaller than to the mtime of the file created after calling this function.
wait = ->
timestampDirectory = temp.mkdirSync('node-pathwatcher-timestamp')
filePath = path.join(timestampDirectory, 'temp')
fs.writeFileSync(filePath, '.')
mtimeOld = fs.statSync(filePath).mtime
mtimeNew = mtimeOld
while mtimeOld >= mtimeNew
fs.writeFileSync(filePath, '.')
mtimeNew = fs.statSync(filePath).mtime
beforeEach ->
isCallbackCalled = false
callback = => isCallbackCalled = true
atom.config.set('notational-velocity.directory', tempDirectory)
fs.writeFileSync(path.join(tempDirectory, 'Readme.md'), 'read me')
wait()
fs.mkdirSync(path.join(tempDirectory, 'Car'))
fs.writeFileSync(path.join(tempDirectory, 'Car', 'Mini.md'), 'mini')
wait()
noteDirectory = new NoteDirectory(tempDirectory, null, callback)
afterEach ->
noteDirectory.destroy()
fs.unlinkSync(path.join(tempDirectory, 'Car', 'Mini.md'))
fs.rmdirSync(path.join(tempDirectory, 'Car'))
fs.unlinkSync(path.join(tempDirectory, 'Readme.md'))
atom.config.set('notational-velocity.directory', defaultDirectory)
it 'gives a list of notes in the order so that the newest one comes first', ->
notes = noteDirectory.getNotes()
expect(notes.length).toEqual(2)
expect(notes[0].getText()).toBe 'mini'
expect(notes[1].getText()).toBe 'read me'
expect(notes[0].getModified().getTime()).toBeGreaterThan(notes[1].getModified().getTime())
it 'changes its order when a note is changed', ->
fs.writeFileSync(path.join(tempDirectory, 'Readme.md'), 'read me new')
wait()
waitsFor -> isCallbackCalled
runs ->
notes = noteDirectory.getNotes()
expect(notes[0].getText()).toBe 'read me new'
expect(notes[1].getText()).toBe 'mini'
it 'changes its order when a note is created', ->
fs.writeFileSync(path.join(tempDirectory, 'Car', 'Prius.md'), 'prius')
wait()
waitsFor -> isCallbackCalled
runs ->
notes = noteDirectory.getNotes()
expect(notes.length).toEqual(3)
expect(notes[0].getText()).toBe 'prius'
expect(notes[1].getText()).toBe 'mini'
expect(notes[2].getText()).toBe 'read me'
fs.unlinkSync(path.join(tempDirectory, 'Car', 'Prius.md'))
it 'changes its order when a note is deleted', ->
fs.unlinkSync(path.join(tempDirectory, 'Car', 'Mini.md'))
wait()
waitsFor -> isCallbackCalled
runs ->
notes = noteDirectory.getNotes()
expect(notes.length).toEqual(1)
expect(notes[0].getText()).toBe 'read me'
# So that it won't spit an error in the teardown stage.
fs.writeFileSync(path.join(tempDirectory, 'Car', 'Mini.md'), 'mini')
it 'changes its order when a note is renamed', ->
oldPath = path.join(tempDirectory, 'Car', 'Mini.md')
newPath = path.join(tempDirectory, 'Mini.md')
fs.renameSync(oldPath, newPath)
wait()
waitsFor -> isCallbackCalled
runs ->
notes = noteDirectory.getNotes()
expect(notes.length).toEqual(2)
expect(notes[0].getTitle()).toBe 'Mini'
expect(notes[1].getTitle()).toBe 'Readme'
# So that it won't spit an error in the teardown stage.
fs.renameSync(newPath, oldPath)
it 'updates properly when a directory is created and a note is created inside it', ->
fs.mkdirSync(path.join(tempDirectory, 'Food'))
fs.writeFileSync(path.join(tempDirectory, 'Food', 'Milk.md'), 'milk')
wait()
waitsFor -> isCallbackCalled
runs ->
notes = noteDirectory.getNotes()
expect(notes.length).toEqual(3)
expect(notes[0].getText()).toBe 'milk'
# So that it won't spit an error in the teardown stage.
fs.unlinkSync(path.join(tempDirectory, 'Food', 'Milk.md'))
fs.rmdirSync(path.join(tempDirectory, 'Food'))
-36
View File
@@ -1,36 +0,0 @@
path = require 'path'
fs = require 'fs-plus'
temp = require 'temp'
pathWatcher = require 'pathwatcher'
Note = require '../lib/note'
describe 'Note', ->
defaultDirectory = atom.config.get('notational-velocity.directory')
tempDirectory = temp.mkdirSync('node-pathwatcher-directory')
tempFilePath = path.join(tempDirectory, 'Temp.md')
beforeEach ->
atom.config.set('notational-velocity.directory', tempDirectory)
fs.writeFileSync(tempFilePath, 'old')
afterEach ->
fs.unlinkSync(tempFilePath)
atom.config.set('notational-velocity.directory', defaultDirectory)
it 'creates a note', ->
note = new Note(tempFilePath, null, null)
expect(note.getTitle()).toBe 'Temp'
expect(note.getText()).toBe 'old'
expect(note.getFilePath()).toBe tempFilePath
note.destroy()
it 'modifies a note', ->
note = new Note(tempFilePath, null, null)
expect(note.getText()).toBe 'old'
fs.writeFileSync(tempFilePath, 'new')
oldModified = note.getModified()
waitsFor -> oldModified != note.getModified()
runs ->
expect(note.getText()).toBe 'new'
note.destroy()