meemo-app
Version:
A personal ideas, notes or links manager
373 lines (293 loc) • 11.9 kB
JavaScript
(function () {
;
var Core = window.Guacamoly.Core;
// poor man's async in the global namespace
window.asyncForEach = function asyncForEach(items, handler, callback) {
var cur = 0;
if (items.length === 0) return callback();
(function iterator() {
handler(items[cur], function (error) {
if (error) return callback(error);
if (cur >= items.length-1) return callback();
++cur;
iterator();
});
})();
};
// Tag propasal filter
Vue.getCurrentSearchWord = function (search, inputElement) {
var cursorPos = $(inputElement)[0] ? $(inputElement)[0].selectionStart : -1;
var word = '';
if (cursorPos === -1) return '';
for (var i = 0; i < search.length; ++i) {
// break if we went beyond and we hit a space
if (i >= cursorPos && (search[i] === ' ' || search[i] === '\n')) break;
if (search[i] === ' ' || search[i] === '\n') word = '';
else word += search[i];
}
return word;
};
function proposeTags(options, search, inputSelector, requireHash, threshold) {
var raw = Vue.getCurrentSearchWord(search, $(inputSelector));
if (requireHash && raw[0] !== '#') return [];
var word = raw.replace(/^#/, '');
if (threshold && threshold > word.length) return [];
return options.filter(function (o) {
return o.name.indexOf(word) >= 0;
});
}
Vue.filter('proposeTags', proposeTags);
Vue.filter('proposeTagsThingsEdit', function (options, search, id) {
return proposeTags(options, search, $('#textarea-' + id), true, 1);
});
function popularTags(options, amount) {
amount = amount || 15;
return options.slice().sort(function (a, b) { return b.usage - a.usage; }).slice(0, amount);
}
Vue.filter('popularTags', popularTags);
var vue = new Vue({
el: '#application',
data: {
Core: window.Guacamoly.Core,
tags: [],
things: [],
busyAdd: false,
busyThings: true,
busyFetchMore: false,
search: '',
archived: false,
sticky: false,
profile: {},
settings: {},
mainView: '',
thingContent: '',
thingAttachments: [],
uploadProgress: -1
},
methods: {
giveAddFocus: function () {
$('#addTextarea').focus();
},
addThing: function () {
var that = this;
this.busyAdd = true;
Core.things.add(this.thingContent, this.thingAttachments, function (error, thing) {
that.busyAdd = false;
if (error) return console.error(error);
that.thingContent = '';
that.thingAttachments = [];
that.things.unshift(thing);
that.refreshTags();
});
},
refreshTags: function (callback) {
var that = this;
Core.tags.get(function (error, tags) {
if (error) return console.error(error);
that.tags = tags;
if (callback) callback();
});
},
refresh: function (search) {
var that = this;
this.busyThings = true;
window.location.href = '/#search?' + (search ? encodeURIComponent(search) : '');
Core.things.get(search || '', this.archived, function (error, data) {
if (error) return console.error(error);
that.things = data;
that.busyThings = false;
});
},
triggerAttachmentUpload: function () {
$('#addAttachment').click();
},
attachmentChanged: function (event) {
var that = this;
var count = event.target.files.length;
var stepSize = 100 / count;
var currentIndex = 0;
this.uploadProgress = 1;
asyncForEach(event.target.files, function (file, callback) {
var formData = new FormData();
formData.append('file', file);
that.$root.Core.things.uploadFile(formData, function (progress) {
var tmp = Math.ceil(stepSize * currentIndex + (stepSize * progress));
that.uploadProgress = tmp > 100 ? 100 : tmp;
}, function (error, result) {
if (error) return callback(error);
currentIndex++;
that.thingContent += ' [' + result.fileName + '] ';
that.thingAttachments.push(result);
callback();
});
}, function (error) {
if (error) console.error('Error uploading file.', error);
that.uploadProgress = -1;
});
},
activateProposedTag: function (tag) {
var word = Vue.getCurrentSearchWord(this.thingContent, $('#addTextarea'));
if (!word) console.log('nothing to add');
var cursorPosition = $('#addTextarea')[0].selectionStart;
this.thingContent = this.thingContent.replace(new RegExp(word, 'g'), function (match, offset) {
return ((cursorPosition - word.length) === offset) ? ('#' + tag.name) : match;
});
Vue.nextTick(function () { $('#addTextarea').focus(); });
},
// prevent from bubbling up to the main drop handler to allow textarea drops and paste
preventEventBubble: function (event) {
event.cancelBubble = true;
},
dragOver: function (event) {
event.preventDefault();
},
dropOrPasteHandler: dropOrPasteHandler,
main: function () {
var that = this;
this.mainView = 'loader';
Core.session.profile(function (error, profile) {
if (error) return console.error(error);
that.profile = profile.user;
Core.settings.get(function (error, settings) {
if (error) return console.error(error);
// set initial settings
that.settings = settings;
if (settings.title) window.document.title = settings.title;
if (settings.backgroundImageDataUrl) window.document.body.style.backgroundImage = 'url("' + settings.backgroundImageDataUrl + '")';
that.refreshTags(function () {
that.mainView = 'content';
window.setTimeout(function () { $('#searchBarInput').focus(); }, 0);
hashChangeHandler();
// add global object for browser extensions
document.getElementById('guacamoly-settings-node').textContent = JSON.stringify({
origin: Core.origin(),
token: Core.token(),
title: settings.title
});
});
});
});
}
},
ready: function () {
// Register event handlers
shortcut.add('Ctrl+s', this.addThing.bind(this), { target: 'addTextarea' });
shortcut.add('Ctrl+Enter', this.addThing.bind(this), { target: 'addTextarea' });
shortcut.add('Ctrl+f', function () { $('#searchBarInput').focus(); }, {});
this.main();
}
});
function hashChangeHandler() {
var action = window.location.hash.split('?')[0];
var params = window.location.hash.indexOf('?') > 0 ? decodeURIComponent(window.location.hash.slice(window.location.hash.indexOf('?') + 1)) : null;
if (action === '#search') {
if (params !== null) vue.search = params;
vue.refresh(vue.search);
} else {
window.location.href = '/#search?';
}
}
function scrollHandler() {
// add 1 full pixel to be on the safe side for zoom settings, where pixel values might be floats
if ($(window).height() + $(window).scrollTop() + 1 >= $(document).height()) {
// prevent from refetching while in progress
if (vue.busyFetchMore) return;
vue.busyFetchMore = true;
Core.things.fetchMore(function (error, result) {
vue.busyFetchMore = false;
if (error) return console.error(error);
vue.things = vue.things.concat(result);
});
}
}
function dropOrPasteHandler(event) {
event.cancelBubble = false;
var data;
if (event.type === 'paste') data = event.clipboardData.items;
else if (event.type === 'drop') data = event.dataTransfer.items;
else return;
for (var i = 0; i < data.length; ++i) {
if (data[i].kind === 'string') {
// stop if we got a string on a textarea native handling is better
if (event.target.localName === 'textarea') continue;
if (data[i].type.match('^text/plain')) {
data[i].getAsString(function (s) {
vue.thingContent = vue.thingContent + s;
});
event.cancelBubble = true;
event.preventDefault();
} else {
console.log('Drop type', data[i].type, 'not supported.');
}
} else if (data[i].kind === 'file') {
var formData = new FormData();
var file = data[i].getAsFile();
// find unused filename
var j = 0;
var name = file.name;
while (vue.thingContent.indexOf(name) !== -1) {
name = j + '_' + file.name;
++j;
}
formData.append('file', file, name);
Core.things.uploadFile(formData, function () {}, function (error, result) {
if (error) console.error(error);
vue.thingContent += ' [' + result.fileName + '] ';
vue.thingAttachments.push(result);
});
event.cancelBubble = true;
event.preventDefault();
} else {
console.error('Unknown drop type', data[i].kind, data[i].type);
}
}
}
function reset() {
vue.mainView = 'login';
vue.things = [];
vue.tags = [];
vue.search = '';
vue.settings = {};
window.document.title = 'Meemo';
window.document.body.style.backgroundImage = '';
}
Core.onAuthFailure = reset;
Core.onLogout = reset;
Core.settings.onChanged(function (data) {
vue.settings.title = data.title || 'Meemo';
vue.settings.backgroundImageDataUrl = data.backgroundImageDataUrl;
vue.settings.wide = data.wide;
vue.settings.wideNavbar = data.wideNavbar;
vue.settings.keepPositionAfterEdit = data.keepPositionAfterEdit;
vue.settings.publicBackground = data.publicBackground;
vue.settings.showTagSidebar = data.showTagSidebar;
window.document.title = data.title;
if (data.backgroundImageDataUrl) window.document.body.style.backgroundImage = 'url("' + data.backgroundImageDataUrl + '")';
else window.document.body.style.backgroundImage = '';
});
Core.things.onDeleted(function (thing) {
// remove if found
for (var i = 0; i < vue.things.length; ++i) {
if (vue.things[i].id === thing.id) {
vue.things.splice(i, 1);
return;
}
}
});
Core.things.onEdited(function (thing) {
if ((thing.archived && !vue.archived) || (!thing.archived && vue.archived)) {
// remove if found
for (var i = 0; i < vue.things.length; ++i) {
if (vue.things[i].id === thing.id) {
vue.things.splice(i, 1);
return;
}
}
} else {
// move to first spot
vue.things.splice(0, 0, vue.things.splice(vue.things.indexOf(thing), 1)[0]);
}
});
window.addEventListener('hashchange', hashChangeHandler, false);
window.addEventListener('scroll', scrollHandler, false);
})();