<file_sep>Template.notePage.helpers({
    url: function() { return this.url; }
});
<file_sep>Template.treeSubmit.helpers({
    tree: getTreeOptions
});

Template.treeSubmit.events({
    'submit form': function(e) {
        e.preventDefault();

        var tree = {
            name: $(e.target).find('[name=name]').val(),
            parent: $(e.target).find('[name=parent]').val()
        };

        Meteor.call('treeInsert', tree, function(error, result) {
            // display the error to the user and abort
            if (error)
                return alert(error.reason);
            
            // show this result but route anyway
            if (result.branchExists) {
                alert('This branch already exists');
                return;
            }
            
            Router.go('treeManage');
        });
    }
});<file_sep>Template.search.helpers({
    notes: function() {
        return Notes.find({ section: null },
                              { sort: {name: 1} });
    }
});
<file_sep>QuickNote
=========

QuickNote is a note-taking website implemented in [Meteor](https://www.meteor.com).

QuickNote main features are to:

- Organize the notes in a tree structure
- Use the [Markdown](http://daringfireball.net/linked/2014/01/08/markdown-extension) language to enter rich text

I'm not sure if anybody else would be interested in such a project, but you are welcome to give it a spin, especially if you are interested in learning Meteor. In order to work, this project requires the following [Meteor packages](https://atmospherejs.com/):

- accounts-password (Password support for accounts)
- copleykj:jquery-autosize (Automatically adjust textarea height based on content)
- ian:accounts-ui-bootstrap-3  (Bootstrap-styled accounts-ui)
- iron:router (Routing specifically designed for Meteor)
- markdown (Markdown-to-HTML processor)
- meteor-platform (Include a standard set of Meteor packages)
- mizzao:bootstrap-3 (HTML, CSS, and JS framework)
- mystor:device-detection (Client-Side Device Type Detection)
- sacha:spin (Simple spinner package for Meteor)
- underscore (Collection of small helpers: _.map, _.eac..)<file_sep>Tree = new Mongo.Collection('tree');

getTreeOptions = function getTreeOptions() {
    var tree = getTree();
    return getOptions(tree, '');
}

function getOptions(branches, padding) {
    var result = [];
    _.each(branches, function(branch) {
        result.push({ id: branch.element._id, label: (padding + branch.element.name) });
        result = result.concat(getOptions(branch.children, padding + branch.element.name + ' / '));
    });
    return result;
}

function ownsTree(userId, tree) { return tree.userId == userId; }

Tree.allow({
    insert: function(userId, tree) { return true; },
    update: ownsTree,
    remove: ownsTree
});

Tree.deny({
    update: function(userId, tree, fieldNames) {
        // may only edit the following two fields:
        return (_.without(fieldNames, 'name', 'path', 'parent', 'level', 'collapse').length > 0); }
});

Meteor.methods({
    treeInsert: function(treeAttributes) {
        check(Meteor.userId(), String);
        check(treeAttributes, {
            name: String,
            parent: String
        });
        var user = Meteor.user();

        var existingBranch = Tree.findOne({name: treeAttributes.name, parent: treeAttributes.parent, userId: user._id});
        if (typeof existingBranch != 'undefined') {
            return {
                branchExists: true,
                _id: existingBranch._id
            }
        }

        var tree = _.extend(treeAttributes, {
            userId: user._id,
            submitted: new Date()
        });

        var treeId = Tree.insert(tree);

        return {
            _id: treeId
        };
    }
});
<file_sep>Meteor.subscribe('notes');
Meteor.subscribe('tree');<file_sep>Meteor.publish('notes', function() {
    return Notes.find({ userId: this.userId });
});

Meteor.publish('tree', function() {
    return Tree.find({ userId: this.userId });
});<file_sep>// This section sets up some basic app metadata,
// the entire section is optional.
App.info({
  id: 'com.meteor.quicknote',
  name: 'QuickNote',
  description: 'QuickNote'
});<file_sep>Router.configure({
    layoutTemplate: 'layout',
    loadingTemplate: 'loading',
    notFoundTemplate: 'notFound',
    waitOn: function() { return Meteor.subscribe('notes'); }
});

Router.route('/', {
    name: 'notesList',
    data: function() { section = 'all'; return 'all'; },
});

Router.route('/sections/:_id', {
    name: 'sections',
    template: 'notesList',
    data: function() { section = this.params._id; return this.params._id; },
});

Router.route('/notes/:_id', {
    name: 'notePage',
    data: function() { section = "record";
                       note = Notes.findOne(this.params._id);
                      return note;
                    }
});

Router.route('/notes/:_id/edit', {
    name: 'noteEdit',
    data: function() { return Notes.findOne(this.params._id); }
});

Router.route('/submit', {name: 'noteSubmit'});

Router.route('/search', {
    name: 'search',
    data: function() { section = ''; }
});

Router.route('/tree', {
    name: 'treeManage',
    template: 'treeManage',
    data: function() { section = ''; }
});

Router.route('/tree/:_id/edit', {
    name: 'treeEdit',
    data: function() { section = this.params._id; return Tree.findOne(this.params._id); }
});

Router.route('/tree/submit', {
    name: 'treeSubmit',
    data: function() { section = ''; }
});

var requireLogin = function() {
    if (! Meteor.user()) {
        if (Meteor.loggingIn()) {
            this.render(this.loadingTemplate);
        } else {
            this.render('accessDenied');
        }
    } else {
        this.next();
    }
}

Router.onBeforeAction('dataNotFound', {only: 'notePage'});
Router.onBeforeAction(requireLogin);
<file_sep>
Template.treeEdit.helpers({
    tree: getTreeOptions,
    isParent: function(arg) { return (this.id == arg.parent); },
    isCollapse: function() {
        var val = Tree.findOne(section).collapse
        return (typeof val != 'undefined' && val == 'on') ? 'checked' : '';
    }
});

Template.treeEdit.events({
    'submit form': function(e) {
        e.preventDefault();

        var currentTreeId = this._id;
        var branch = this;

        var treeProperties = {
            name: $(e.target).find('[name=name]').val(),
            parent: $(e.target).find('[name=parent]').val(),
            collapse: $('input[name=collapse]:checked').val()
        }
        if (typeof treeProperties.collapse == 'undefined') treeProperties.collapse = '';

        if (treeProperties.parent == this._id) {
            alert('A branch cannot be its own parent');
            return;
        }

        Tree.update(currentTreeId, {$set: treeProperties}, function(error) {
            if (error) {
                // display the error to the user
                alert(error.reason);
            } else {
                Router.go('treeManage');
            }
        });
    },

    'click .delete': function(e) {
        e.preventDefault();
        if (confirm("Delete this branch?")) {
            var currentTreeId = this._id;

            // Checks that there is no subtree
            var subtrees = Tree.findOne({parent: this._id});
            if (typeof subtrees != 'undefined') {
                alert('Please delete any subtree before');
                return;
            }

            // Checks there are no note in the branch
            var notes = Notes.findOne({section: this._id});
            if (typeof notes != 'undefined') {
                alert('Please delete any associated note before');
                return;
            }
            Tree.remove(currentTreeId);
            Router.go('treeManage');
        }
    }
});<file_sep>Template.noteItem.helpers({
    ownNote: function() {
        return this.userId === Meteor.userId();
    },
    contentSummary: function() {
        if (section == 'record') return this.content;
        return this.content.split('\n').splice(0, 5).join('\n');
    }
});