diff --git a/lib/notational-velocity-view.coffee b/lib/notational-velocity-view.coffee index 7a5c276..b3a9c98 100644 --- a/lib/notational-velocity-view.coffee +++ b/lib/notational-velocity-view.coffee @@ -2,69 +2,54 @@ 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(filterQuery + titleItem.getTitle().slice(filterQuery.length)) - 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' @@ -72,30 +57,35 @@ 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() - filePath = null + item = @getSelectedItem() + filePath = null + sanitizedQuery = @getFilterQuery().replace(/\s+$/, '') + calculatedPath = path.join(@rootDirectory, sanitizedQuery + '.md') if item? - filePath = item.getFilePath() - else - sanitizedQuery = @getFilterQuery().replace(/\s+$/, '') - if sanitizedQuery.length > 0 - filePath = path.join(@rootDirectory, sanitizedQuery + '.md') - fs.writeFileSync(filePath, '') + 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) -> @@ -126,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() @@ -147,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. diff --git a/lib/notational-velocity.coffee b/lib/notational-velocity.coffee index 6ea72f7..0d7553a 100644 --- a/lib/notational-velocity.coffee +++ b/lib/notational-velocity.coffee @@ -44,7 +44,7 @@ module.exports = serialize: -> notationalVelocityViewState: @notationalVelocityView.serialize() - createView: (state) -> + createView: (state, docQuery) -> unless @notationalVelocityView? NotationalVelocityView = require './notational-velocity-view' @notationalVelocityView = new NotationalVelocityView(state.notationalVelocityViewState) diff --git a/lib/note-directory.coffee b/lib/note-directory.coffee deleted file mode 100644 index 88adab0..0000000 --- a/lib/note-directory.coffee +++ /dev/null @@ -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() diff --git a/lib/note.coffee b/lib/note.coffee deleted file mode 100644 index b313488..0000000 --- a/lib/note.coffee +++ /dev/null @@ -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 diff --git a/package.json b/package.json index f438a4b..2f11aac 100644 --- a/package.json +++ b/package.json @@ -20,16 +20,14 @@ }, "homepage": "https://github.com/seongjaelee/notational-velocity", "dependencies": { - "fs-plus": "2.x", "atom-space-pen-views": "^2.0.3", - "pathwatcher": "^4.2", + "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", - "underscore-plus": "^1.6.6", - "temp": "~0.7.0" + "underscore-plus": "^1.6.6" } } diff --git a/spec/note-directory-spec.coffee b/spec/note-directory-spec.coffee deleted file mode 100644 index 44cfa2d..0000000 --- a/spec/note-directory-spec.coffee +++ /dev/null @@ -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')) diff --git a/spec/note-spec.coffee b/spec/note-spec.coffee deleted file mode 100644 index 3bce89d..0000000 --- a/spec/note-spec.coffee +++ /dev/null @@ -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()